2024년 6월 28일 금요일

Vuejs Provide와Inject 로 컴퍼넌트간 소

Vuejs Provide와Inject 로 컴퍼넌트간 소

Vue.js 제공 및 주입: 구성 요소 간 통신을 위한 고급 방법

소개

Vue.js는 구성 요소 간 통신을 위한 다양한 방법을 제공하는 인기 있는 프런트 엔드 프레임워크이다. 이 중 provide 및 Inject은 prop이나 eventemit를 통해 수동으로 데이터를 전달할 필요 없이 중첩된 구성 요소 트리에 걸쳐 데이터를 전달할 수 있는 한 쌍의 고급 옵션이다.

provide은 ( inject는) 무엇입니까 ?

provide 및 Inject은 구성 요소 간의 종속성 주입을 구현하기 위해 Vue.js 2.2.0+에 도입된 한 쌍의 옵션이다. provide을 사용하면 하위 구성 요소가 얼마나 깊이 중첩되어 있는지에 관계없이 상위 구성 요소에 데이터를 제공하고 Inject을 통해 하위 구성 요소의 해당 데이터에 액세스할 수 있다.

provide그리고 inject의 원리

provide의 원칙 Vue.js 구성 요소 인스턴스의 상위-하위 관계를 기반으로 한다. 구성 요소가 provide를 통해 데이터를 제공하면 해당 데이터가 상위 구성 요소의 개체에 추가된다 provide. 그러면 하위 구성요소는 inject옵션을 통해 이 데이터를 얻을 수 있다.

provide 및 inject 원칙은 Vue.js 구성 요소 인스턴스의 상위-하위 관계를 기반으로 한다. 구성 요소가 제공을 통해 데이터를 제공하면 해당 데이터가 상위 구성 요소의 제공 개체에 추가된다. 그러면 하위 구성 요소는 주입 옵션을 통해 이 데이터를 얻을 수 있다.

예제를 통해 작업을 제공하고 주입하는 방법을 자세히 살펴보겠습니다.

<template>
 <div>
 <p>부모 데이터:{{ ancestorData }}</p>
 <child-component></child-component>
 </div>
</template>
<script>
import ChildComponent from './ChildComponent';
export default {
 provide: {
 ancestorData: '이것은 상위 구성요소의 데이터이다.'
 },
 components: {
 ChildComponent
 }
};
</script>

위의 예에서 조상 구성 요소는 provide 옵션을 통해 조상 데이터라는 데이터를 제공한다. 하위 구성 요소 ChildComponent는 inject 옵션을 통해 이 데이터를 얻을 수 있다.

<template>
 <div>
 <p>자식컴퍼넌트:{{ injectedData }}</p>
 </div>
</template>
<script>
export default {
 inject: ['ancestorData'],
 data() {
 return {
 injectedData: this.ancestorData
 };
 }
};
</script>

하위 구성 요소에서는 주입 옵션을 사용하여 액세스하려는 데이터를 선언한 다음 데이터에 주입된 데이터로 저장한다.

provide 및 inject의 사용법

provide 및 inject은 주로 다음과 같은 상황에서 사용된다.

  1. 전역 구성: 루트 구성 요소에서 제공 옵션을 사용하여 전역 구성 정보를 제공한 다음 삽입을 사용하여 모든 구성 요소에서 이 정보에 액세스할 수 있다.

  2. 교차 레벨 통신: provide 및 inject을 통해 소품이나 이벤트를 통해 수동으로 데이터를 전달할 필요 없이 구성 요소 트리를 통해 데이터를 전달할 수 있다.

  3. 공유 인스턴스: 상위 구성 요소에서 공유 인스턴스를 만든 다음 이 인스턴스를 하위 구성 요소에서 공유할 수 있다.

  4. 플러그인 개발: 플러그인 가능한 플러그인이나 라이브러리를 개발하는 경우 구성이나 서비스를 쉽게 공유할 수 있는 방법을 제공하고 주입한다.

provide및 inject의 고급 사용법

기본값

데이터가 제공되지 않는 경우 주입에 대한 기본값을 제공할 수 있다. 이는 개체의 제공 속성을 래핑하고 기본 속성을 설정하여 달성할 수 있다.

<template>
 <div>
 <p>자식컴퍼넌트:{{ injectedData }}</p>
 </div>
</template>
<script>
export default {
 inject: {
 ancestorData: { default: '기본 데이터' }
 },
 data() {
 return {
 injectedData: this.ancestorData
 };
 }
};
</script>

위의 예에서 상위 구성 요소가 데이터를 제공하지 않으면 ancestorData 하위 구성 요소는 기본값을 사용한다. .

동적 provide

동적 제공 제공 옵션은 데이터를 동적으로 제공할 수 있는 기능을 허용할 수 있다. 이는 사용자의 로그인 상태에 따라 다양한 사용자 데이터를 제공하는 등 특정 시나리오에서 유용하다.

<template>
 <div>
 <p>사용자 데이터:{{ userData }}</p>
 </div>
</template>
<script>
export default {
 provide() {
 return {
 userData:js
 this.loggedIn ? this.user : null
 };
 },
 data() {
 return {
 loggedIn: true,
 user: {
 name: 'ninja_zero',
 email: 'ninja_zero@dx-ninja.com'
 }
 };
 }
};
</script>

위의 예에서 제공 옵션은 userData 속성이 포함된 객체를 반환한다. 사용자가 로그인한 경우 userData 속성에는 사용자 데이터가 포함된다. 그렇지 않으면 null이 된다.

동적 inject

inject 옵션은 함수를 허용하여 동적으로 데이터를 얻을 수도 있다. 이는 조건에 따라 어떤 데이터를 주입할지 결정해야 할 때 유용하다.
코드 복사

<template>
 <div>
 <p>사용자 데이터:{{ userData }}</p>
 </div>
</template>
<script>
export default {
 inject: ['user'],
 computed: {
 userData() {
 if (this.user) {
 return `이름:${this.user.name}, 메일:${this.user.email}`;
 } else {
 return '미등록';
 }
 }
 }
};
</script>

위의 예에서는 inject를 사용하여 사용자 데이터를 주입한다. 그런 다음 계산된 속성 userData를 사용하여 사용자가 로그인했는지 여부에 따라 사용자 데이터를 동적으로 생성한다.

사용 시나리오

  1. 여러 구성 요소가 일부 공통 데이터나 상태를 공유해야 하는 경우 Provide 및 inject를 사용하여 이 데이터를 공유할 수 있다. 최상위 컴포넌트에 데이터를 제공한 후 주입을 통해 모든 하위 컴포넌트에서 사용한다. 이러한 방식으로 데이터를 편리한 방식으로 공유할 수 있으며 구성 요소 간에 데이터가 여러 번 전송되는 것을 방지할 수 있다. 플러그인 또는 타사 라이브러리 통합: 제공 및 주입은 일부 플러그인이나 타사 라이브러리의 기능을 구성 요소에 캡슐화하고 다른 구성 요소가 해당 기능을 공유하도록 해야 할 때 사용할 수 있다.
  2. 크로스레벨 구성 요소 통신: 구성 요소 계층 구조가 깊고 서로 다른 수준의 구성 요소 간에 데이터를 전송해야 하는 경우 제공 및 주입을 사용할 수 있다.
    상위 구성 요소는 제공을 통해 데이터를 제공하고 하위 구성 요소는 주입 주입을 사용한다. 이러한 방식으로 레이어별로 소품을 전달하거나 통신을 위해 이벤트 버스를 사용하는 것을 피할 수 있다.
  3. 플러그인 또는 타사 라이브러리 통합 :일부 플러그인이나 타사 라이브러리의 기능을 구성 요소로 캡슐화하고 다른 구성 요소가 해당 기능을 공유하도록 해야 할 때 provide및 inject 를 사용할 수 있다.

단점

데이터의 소스는 추적할 수 없다 . 모든 수준에서 액세스할 수 있으므로 데이터 추적이 어렵습니다. 어느 레이어에서 이를 선언하는지, 어느 레이어에서 이를 사용하는지 알 수 없다.
구성 요소 간 결합을 유발한다 . 구성 요소 간 결합을 유발하여 구성 요소 재사용성에 영향을 줍니다.

요약

provide및 inject은 Vue.js에서 구성 요소 간의 여러 계층에 걸쳐 데이터를 전달하는 고급 방법이다. 이는 글로벌 구성, 교차 레벨 통신, 공유 인스턴스 및 플러그인 개발과 같은 다양한 시나리오에 적합한다. 제공 및 주입의 원리와 사용법을 깊이 이해하면 Vue.js의 기능을 더 잘 활용하고 보다 대화형이고 반응성이 뛰어난 프런트 엔드 애플리케이션을 구축하는 데 도움이 된다.

추가로 컴퍼넌트간 통신하는 방법이 더 있는데 간단히 소개해본다.

방법 1. props/$emit : 부모-자식이 끈끈하게 연결되어 있는구조로서 다층복합으로 구성된 경우에는 prop과 emit 지옥이 펼쳐진다.
방법 2, emit/emit/on : 한쪽이 emit과 함께 데이터를 던지면 on 으로 받는쪽에서 받은 자료를 처리하는구조이다. emit과 on 의 개수가 많아지면 관리가 어렵다.
방법 3. vuex : vue에서 전역 혹은 모듈별로 저장소를 통해서 데이터 전달이 가능하다. dispatch 와 commit, getter 로 관리되며, 별도의 저장소를 통해 데이터를 관리하기 때문에 단순히 부모자식 이 일대일의 간단한 구조에서 컴퍼넌트간에 데이터를 전달할때는 다소 과잉된 방법이기도 하다.
방법 4. attrs/attrs/listeners : Vue2.4에 등장한 속성-리스너 방식으로 여러 대상에 대한 데이터전달할경우에 유용하다.
방법 5, provide/inject : 본 글에서 설명한 내용이다.
방법 6, $parent/ $children그리고ref : 구성요소를 직접 참고하는 것으로 깊은 커플링으로 인해 사용을권하지 않는다.
방법 7, v-model : v-model은 Vue.js에서 제공하는 Syntactic Sugar를 기반으로 한 컴포넌트 통신 방법이며, v-model이 지정된 컴퍼넌트는 v-model에 정의된 값을 동시에 변경가능한다.

2024년 6월 26일 수요일

Vuejs 테스트코드작성

Vuejs 테스트코드작성

Vue.js 에서 테스트 코드를 작성해보자.(jest)

– 작성중 … –
Vue 는 html 화면을 구성시켜주는 프레임워크이다. 따라서 화면을 구성하는 각 컴퍼넌트들을 어떻게 테스트할지에 대해 고민하게 되는데 다행히 test-util 이라는 것을 제공해 주어 화면에 구성요소 확인, 클릭, 함수 실행 이벤트수행여부 등을 시뮬레이션하여 테스트 해 볼수 있게 해준다.

테스트 라이브러리 설치는 어렵지 않다.
먼저 javascript 테스트 프로그램인 jest 를 설치하고, jest를 vue에서 연동해서 컴퍼넌트등을 좀더 쉽게 테스트 할수 있도록 해주는 vue-jest, test-utils 를 설치하면된다.

테스트 라이브러리 설치

Vue CLI 로 Vue2나 Vue3 을 설치했다면 test 관련 모듈이 설치가 안되어있을수 있으므로 아래의 라이브러리를 추가로 설치하자.

설명

@vue/cli-plugin-unit-jest@^4.5.14 : Vue CLI에서 제공하는 Jest 플러그인입니다. 이 플러그인을 사용하면 Vue.js 프로젝트에서 Jest를 통해 유닛 테스트를 쉽게 설정하고 실행할 수 있다. 의존하는 관련 모듈인 jest가 같이 설치된다.

@vue/test-utils@1.3.6 : Vue컴퍼넌트에서 발생하는 이벤트나 데이터등을 시뮬레이션 할수 있게 도와주는 라이브러리이다.

vue와 jest 를 연동하는 라이브러리등이 버젼에 따라 vue2 또는 vue3 에 적합하도록 되어 있으므로 아래의 package.json 을 참고하여 자신의 package.json 을 수정한후 npm install 해서 라이브러리를 다시 설치하자.

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test": "vue-cli-service test:unit"
  },
  "dependencies": {
    "@vue/cli-plugin-unit-jest": "^4.5.14",
    "core-js": "^3.6.5",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
     "@vue/test-utils": "1.3.6",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  },
  
  "jest": {
    "preset": "@vue/cli-plugin-unit-jest"
  }

테스트 1


import HelloWorld from '@/components/HelloWorld.vue'
import Vue from 'vue'

let Constructor
let vm
let abc

test('HelloWorld Test', () => {
  Constructor = Vue.extend(HelloWorld)
  vm = new Constructor().$mount()
  abc = vm._data.abc
  expect(abc).toBe(100);
});

2024년 6월 24일 월요일

Vuejs + Lodash

Vuejs + Lodash

Vuejs + Lodash

Rx계열의 프로그램을 접해본 사람들은 데이터 스크림을 효과적으로 다루는 map, reduce, filter, debounce 등의 함수를 통해 원하는 데이터를 효과적으로 취득하는 방법을 알고 있을 것이다.
Lodash 는 Rx 프로그래밍을 할때처럼 데이터(배열,컬렉션등)을 효과적으로 다룰수 있도록 도와주는 라이브러리이다.

Lodash는 일관된 모듈식 고성능 JavaScript 유틸리티 라이브러리이다. 대부분의 개발 요구 사항을 충족할 수 있는 풍부한 기능과 방법을 제공한다. 스캐폴딩을 통해 구축된 Vue 프로젝트에는 Lodash 도구 라이브러리가 설치되어 있어 직접 사용할 수 있다.
Lodash 공식 웹사이트: lodash.com/

lodash 에는 정말 많은 기능들이 탑재되어 있어서, array, collection, data 등의 데이터를 쉽고 간편하게 다룰수있도록 해준다.
Arry관련 함수로는 findIdnex, flatten,remove등과 Collection관련 함수로는 every, find,filter,map, reduce 등을 지원한다.

Array: 데이터 채우기, 요소 찾기, 배열 슬라이싱 및 기타 작업과 같은 배열 유형에 적합
Collection: 배열 및 객체 유형에 적용 가능하며 그룹화, 검색, 필터링 및 기타 작업과 같은 문자열에 부분적으로 적용 가능
Function: 조절, 지연, 캐싱, 후크 설정 등과 같은 기능 유형에 적합
Lang: 다양한 유형에 보편적으로 적용 가능하며 유형 판단 및 유형 변환을 수행하는 데 자주 사용
Math: 숫자 유형에 적합하며 종종 수학 연산을 수행하는 데 사용
Number: 난수를 생성하고 숫자 값과 숫자 간격의 관계를 비교하는 데 적합
Object: 객체 유형에 적용 가능하며 객체 생성, 확장, 유형 변환, 검색, 수집 및 기타 작업에 자주 사용.
Seq: 체인 호출을 생성하고 실행 성능을 향상시키는 데 자주 사용(지연 계산).
String: 문자열 유형에 적합

사용법

Lodash의 첫 번째 소개 방법을 사용 import _ from 'lodash'하거나 import { xx } from 'lodash'. 그런 다음 해당 메서드를 사용하려면 직접 _.xx호출 하면 된다 xx(). 아래 debounce 예를 들어보겠다 `

<template>
 <div 
 class="edit-input" 
 :style="{'font-size':fontSize}"
 v-html="innerText"
 :contenteditable="isEdit"
 @focus="isLock = true"
 @blur="onBlur"
 @input="changeText"
 >
 </div>
</template>
<script>
import _ from 'lodash'
//  import { debounce } from 'lodash'
/*
  
props: {  
  value: { 
    type: String, 
   },  
 },  
 data() {  
   return {  handleInput: null,  }  
 },
mounted() { 
   this.handleInput = debounce(function (e) {     
      console.log('value', e.target.value) this.$emit('input', e.target.value)
      }, 1000) 
},


  


*/

export default {
 methods: { 

 changeText:_.debounce(function(e){
 this.$emit('input',e.target.innerText); 
 }, 500)
 }
}
  
</script>

또는

2024년 6월 23일 일요일

Vuex 모듈 방식개발

Vuex 모듈 방식개발

Vuex 모듈 방식으로 개발

1. 모듈을 사용하는 이유

단일 상태 트리를 사용하기 때문에 애플리케이션의 모든 상태가 상대적으로 큰 개체에 집중된다. 애플리케이션이 매우 복잡해지면 저장소 개체가 상당히 커질 가능성이 있다.

위의 문제를 해결하기 위해 Vuex를 사용하면 저장소를 모듈로 분할할 수 있다. 각 모듈에는 고유한 상태, 변형, 작업 및 getter가 있다.

네임스페이스 기본적으로 모듈 내의 작업, 변형 및 getter는 전역 네임스페이스에 등록된다. 이를 통해 여러 모듈이 동일한 변형 또는 작업에 응답할 수 있다.

모듈이 더 높은 수준의 캡슐화 및 재사용성을 갖기를 원하는 경우, 네임스페이스 모듈을 추가하여 네임스페이스 모듈로 만들 수 있다. 모듈이 등록되면 모듈의 모든 getter, 작업 및 변형 이름이 모듈의 등록된 경로에 따라 자동으로 지정된다.

2. vuex 다운로드

일단 vuex를 설치한다.

npm i vuex yarn add vuex // yarn

3. store.js 수정

기본적으로 전역인 store.js만 있는데 아래처럼 모듈폴더를 추가하고 각 모듈별 저장할 store 파일들을 만든다.

  • src 폴더에 store 폴더 추가
  • store.js를 store 폴더에 넣고 store.js의 이름을 index.js로 바꿉니다.
  • 모듈 js를 저장하려면 store 폴더에 모듈 폴더를 추가한다…

  • type.js 에서 상수를 정의하고 , 상수를 사용하여 mutation을 대체한다. 이벤트 유형 member.js 에서 사용자 구성 요소 에 사용되는 , state를 작성 하고 마지막으로 균일하게 내보낸다(이전 예의 store.js 와 유사) . getters.js 에서는 프로세스 판단 및 비동기 요청과 같이 실행될 작업인 actions.js 에 작성된 원래 속성을 가져오는 데 사용 된다 .

4. main.js 에 index.js(스토어) 등록수정

코드 복사

import Vue from 'vue'
import App from './App.vue'
import store from './store/index.js'
new Vue({
 store,
 el: '#app',
 render: h => h(App)
})

5.types.js에서 상수를 정의하고 내보낸다. 기본적으로 모두 대문자이다.

const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
export default {
 INCREMENT,
 DECREMENT
}

참고 : 이러한 상수를 별도의 파일에 넣으면 코드 공동 작업자가 전체 앱에 포함된 변형을 한눈에 볼 수 있다. 상수를 사용할지 여부는 사용자에게 달려 있다. 이는 여러 사람이 함께 작업하는 대규모 프로젝트에 도움이 될 수 있다.

6. member.js의 사용자 구성 요소에 사용되는 상태, getter, 작업 및 변형을 작성한다.

import types from "./../types";

// state로 관리
const state ={
 count:5
}
// getters
var getters ={
 count(state){
 return state.count
 }
}
// actions : store에 가해질 액션을 정의,commit 을 통해서 액션을 받는다. commit(types.INCREMENT)
const actions ={
 increment({ commit, state }){
 commit(types.INCREMENT)
 },
 decrement({commit,state}){
 if (state.count>10) {
 commit(types.DECREMENT)
 }
 }
}
const mutations ={
 // 액션을 통해 commit(types.INCREMENT) 가 발동할때 실행. 단 상수를 안쓴다면 보통 이렇게 호출한다.  increment(state)
 [types.INCREMENT](state){
 state.count++
 },
 [types.DECREMENT](state){
 state.count--
 }
}
// 외부에서 사용할수 있도록 내보내자.
export default {
 namespaced: true, // 네임스페이스 설정 추가해서 상태,게터,액션,뮤테이션이 모듈 내부에서만 접근하도록 하자.
 state,
 getters,
 actions,
 mutations
}

7. 추가로 모듈의 값을 이용하여 전역 getters.js에 홀수와 짝수를 결정하는 기능을 작성한다.


import members from './modules/member'
const getters = {
 isEvenOrOdd(state){
 return members.state.count % 2 == 0 ? "짝수" : "홀수"
 }
}
export default getters;

8. index.js에 actions.js, getters.js, user.js를 모아서 균일하게 내보낸다.

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import getters from './getters'
import actions from './actions'
import members from './modules/member'

export default new Vuex.Store({
 getters,
 actions,
 modules:{
 members
 }
})

9. AppMain.vue 파일을 수정.

코드 복사

<template>
 <div id="app">
 <button @click="increment">증가</button>
 <button @click="decrement">감소</button>
 <p>{{count}}</p>
 <p>{{isEvenOrOdd}}</p>
 </div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
 name: 'app',
 data () {
 return {
 msg: 'Welcome to Your Vue.js App'
 }
 },
 computed:mapGetters([
 'count',
 'isEvenOrOdd'
 ]),
 methods:mapActions([
 'increment',
 'decrement'
 ])
}
</script>

2024년 6월 18일 화요일

Laravel : MVC아키텍쳐에 대해

Laravel : MVC아키텍쳐에 대해

Laravel : 라라벨의 MVC 아키텍쳐

라라벨의 기본 아키텍쳐는 MVC이다.
Controller : 사용자로부터의 요청을 받아 , View와 Model을 연결
Model : 데이터 처리- 실제로는 Eloquent 에서 담당한다.
View : 화면처리

우리가 알고 있는 MVC 는 범용적인 경우에 적용하는것이고, 라라빌에서의 MVC 는 조금 다르다.

특히 비지니스 로직을 담당하는 Model 을 artisan 으로 생성해 보면 데이터베이스에서 데이터를 꺼내거나 처리하는 Eloquent 가 use 에 추가된다.

use Illuminate\Database\Eloquent\Factories\HasFactory;  
use Illuminate\Database\Eloquent\Model;

app/Http 폴더는 라라벨 프레임워크를 이용하는 것을 전제로 작업을 View , Controller 이나 Middleware 를 만들때 주로 사용된다.

Model 은 직접적으로 View와 연결하지 않고 Controller에서 참조한다.
이렇게 되면 Controller가 요청받은 리퀘스트처리, View의 처리와 Model 에서 전달받은 데이터를 가지고 비지니스 로직까지 품어야 되는데 Controller 가 비대해지는 경향이 있다.

규모가 작은 홈페이라면 라라벨의 기본 구조만으로도 충분하지만, 좀 규모가 있는 개발이라면 기능에 따라 모듈 또는 디렉토리 별로 관리하는것이 좋다.

문제는 서비스가 늘어날수록 View나 Controller가 늘어나는것은 어쩔수없다.

해결 방법은 여러가지가 있겠지만
기능(feature)별로 폴더를 만들어서 그 안에 Domain layer, Data layer 그리고 Presenter까지 세트로 묶는 방식이 기능확장 또는 추가/삭제 시에 좋을거라 생각한다.

즉,
app폴더와 같은 레벨의 폴더인 package 등과 같은 상위 폴더를 만들고 하위로 아래와 구조로 만든다.

Features
  |-StoreDetail
      |-Domains
      |-Models
      |-resources
      |-Presenters
  |-StoreList
      |-Domains
      |-Models
      |-resources
      |-Presenters
Infrastructures
  |-Databases
  |-Apis
  |-FileSystems
  |-Messages
  |-Logs
Shared 를 만들어 
  |-Utils
  |-Constants
  |-Settings

파일과 폴더가 많아 지지만 이렇게 함으로서 각 기능별로 추가/수정/삭제가 용이하고 Presenter를 통해 Controller 가 데이터를 삽입/취득 하게하여 controller의 부담을 경감시킬 수있다.

2024년 6월 17일 월요일

Laravel : Test 하기

Laravel : Test 하기

Laravel 테스트 작성

Laravel에서는 UnitTest와 FeatureTest 의 두가지가 있다.

UnitTest 는 laravel 프레임워크에 의존하지 않는 순수한 php 소스에 대해 테스트한다.
FeatureTest 는 Laravel 프레임워크를 사용한 기능 테스트가 가능하다.

테스트 환경설정

테스트관련 설정값을 phpunit.xml 에서 설정한다.
기본적으로 아래와 같은 형태이다.

    <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"  
      bootstrap="vendor/autoload.php"  
      colors="true"  
    >  
     <testsuites> <testsuite name="Unit">  
     <directory suffix="Test.php">./tests/Unit</directory>  
     </testsuite> <testsuite name="Feature">  
     <directory suffix="Test.php">./tests/Feature</directory>  
     </testsuite> </testsuites> <coverage processUncoveredFiles="true">  
     <include> <directory suffix=".php">./app</directory>  
     </include> </coverage> <php> <server name="APP_ENV" value="testing"/>  
     <server name="BCRYPT_ROUNDS" value="4"/>  
     <server name="CACHE_DRIVER" value="array"/>  
      <!-- <server name="DB_CONNECTION" value="sqlite"/> -->  
     <!-- <server name="DB_DATABASE" value=":memory:"/> -->  <server name="MAIL_MAILER" value="array"/>  
     <server name="QUEUE_CONNECTION" value="sync"/>  
     <server name="SESSION_DRIVER" value="array"/>  
     <server name="TELESCOPE_ENABLED" value="false"/>  
     </php></phpunit>

testsuites 항목에 보면 Unit와 Feature 항목이 있고 각각 디렉토리와 테스트파일로 인식해서 테스트를 실행할 접미사suffix 가 있다.
이곳에 항목을 추가해서 자신만의 디렉토리에서도 테스트가 실행되도록 할수도 있다.
server name=“APP_ENV” value=“testing” 항목이 있는데 .env 파일을 테스트용으로 별도로 만들어 테스트할때는 다른 환경에서 실행되도록 할수도있다.

UnitTest

UnitTest, 단위테스트는 함수레벨에서 프로그램의 특정 부분을 검증하는것이다. 가능한 모든 클래스와 함수는 테스트가 가능해야 되며 테스트가 불가능 하거나 테스트하기 어려운 함수를 만들었다는 것은 잘못 코딩했다는 반증이기도하다.

PHP 에서는 PHPUnit 이라는 PHP 단위 테스트 패키지 를 제공한다. 이는 주로 단위 테스트에 사용된다. 즉, 가능한 가장 작은 구성 요소로 코드를 테스트할 수 있지만 매우 유연하며 단순한 단위 테스트 이상의 용도로 사용할 수 있다.

Laravel에서는 PHPUnit 테스트를 확장한 TestCase라는 클래스를 제공하여 사용자의 테스트 클래스에서 상속받아 테스트를 진행한다.

class ExampleTest extends TestCase  { 
	public function test_example()  
	{  
	    $this->assertTrue(true);  
	}
}

Laravel 설치시에 위와같은 테스트 샘플이 있으므로 한번 테스트를실행해보자.

    php artisan test 

결과
   PASS  Tests\Unit\ExampleTest
  ✓ example
   PASS  Tests\Feature\ExampleTest
  ✓ example

  Tests:  2 passed
  Time:   0.03s

artisan 의 test 명령을 이용하면 Unit 테스트 와 Feature 테스트 전부 실행된다.
Unit 만 실행하고 싶을때는 아래와 같이 한다.

php artisan test --testsuite=Unit

또는 하나의 파일만 테스트 할수도 있다.

php artisan test tests/Unit/ExampleTest.php

FeatureTest ( 애플리케이션 테스트)

여러 개의 코드 유닛이 함께 작동하여 시스템의 특정 기능이 예상대로 동작하는지 검증하는 테스트이다. 주로 컨트롤러, 라우팅, 데이터베이스 쿼리 등을 포함한 여러 시스템 요소를 통합적으로 테스트한다.

Http 나 데이터 베이스 접속 등을 이용한 테스트가 가능하다.

use RefreshDatabase; // use RefreshDatabase 는 테스트 할때 저장한 데이터를 테스트 종료와 함께 삭제해준다.

public function testUser()
{
    $user = User::factory()->create(); //$user 데이터 생성

    $response = $this->actingAs($user)  //actingAs 함수을 사용하여 사용자 인증을 처리
                     ->getJson('/api/users');  //getJson 함수을 사용하여 JSON 응답받음

    $response->assertStatus(200)
             ->assertJson(['data' => true]); //assertJson  추가 검증
}

Feature 만 실행하고 싶을때는 아래와 같이 한다.

php artisan test --testsuite=Feature

Mock 테스트

모의 테스트(Mock Testing) 은 테스트 대상이 되는 클래스나 함수에 영향을 줄만한 다른 함수의 결과치를 모의(Mocking) 해서 지정된 결과치가 나오도록 하여 실제로 테스트 대상 함수가 해당 결과치를 이용한 수행결과가 테스트결과 예상치와 같은 지 확인하기 위해서 사용한다.

즉, foo() 함수를 실행하는데 foo 함수내부에서는 bar 를 사용한 결과를 가지고 추가 처리를 하여 최종 결과를 리턴하는 기능을 할때, foo 함수를 테스트 할때 bar 결과치에 따라 달라지거나, 실패가 발생할때 원인이 bar 에 의한 건지 foo 에 의한 건지 모호할때가 있다.

이때 bar 함수를 테스트에 필요한 지정된 값만 출력되도록 모의(mocking) 하여 foo 함수가 해당값을 이용하여 처리한 결과가 성공인지 판단 할수 있도록 해준다.

샘플을 보자

namespace App\Services;

class FooBarService
{
    public function bar()
    {
        // 복잡한 로직이나 외부 서비스 호출 등
        return 'real result';
    }

    public function foo()
    {
        $barResult = $this->bar();
        // bar의 결과에 따라 foo의 동작이 달라짐
        if ($barResult === 'real result') {
            return 'foo with real result';
        }
        
        return 'foo with different result';
    }
}

테스트 해보자

    class FooBarServiceTest extends TestCase
{
    public function testFooWithMockedBar()
    {
        // FooBarService의 Mock 객체 생성
        $fooBarServiceMock = $this->getMockBuilder(FooBarService::class)
            ->onlyMethods(['bar'])
            ->getMock();

        // bar 메서드가 호출되면 'mocked result'를 반환하도록 설정
        $fooBarServiceMock->method('bar')->willReturn('mocked result');

        // foo 메서드 실행
        $result = $fooBarServiceMock->foo();

        // foo 메서드가 예상하는 결과를 반환하는지 확인
        $this->assertEquals('foo with different result', $result);
    }
}

실행해 보면 아래 처럼 테스트가 성공한다.

   PASS  Tests\Unit\FooBarServiceTest
  ✓ foo with mocked bar

  Tests:  1 passed
  Time:   0.35s

getMockBuilder 로 FooBarService 클래스의 bar() 함수만 모의로 생성한다음 willReturn 으로 ‘mock result’ 라는 고정된 결과가 나오도록 하여 우리가 테스트할 foo 에서 해당 값을 가지고 테스트 하다록 하였다.
foo 에서는 ‘mock result’ 라는 인수가 들어올때 'foo with different result’라는 결과를 출력하므로 참이 되어 테스트가 성공했다.

createMock & getMockBuilder

테스트대상 이외의 클래스나 함수에 대해 지정된 모의(mock)결과를 리턴하도록 도와주는 함수들이다.

createMock : 지정한 클래스에 대해 클래스내의 public 함수에 대해 무조건 null을 리턴하는 모의 class를 만들어준다.

$stub = $this->createMock(SomeClass::class);

getMockBuilder 는 여러가지 설정사항을 직접 선택하여 모의객체를 생성할수있다.

        $mock = $this->getMockBuilder(SomeClass::class)
                 ->onlyMethods(['method1'])
                 ->disableOriginalConstructor() 
                 ->setConstructorArgs(['arg1', 'arg2'])
                 ->disableOriginalClone()
                 ->disableAutoload()
                 ->enableArgumentCloning()
                 ->getMock();

생성자실행안함, 특정함수만 이용,인수지정 등을 사용할수있다.

will

모의객체나 함수에 대해 리턴값을 지정할수있다. 리턴값을 지정함으로서 실제로 테스트 대상이 되는 함수에 대해 신뢰성있는 테스트 결과를 얻을수있다.

$mock->method('fuga')->will($this->returnValue('foo'));

wil 은
will( this>returnValue(this->returnValue(value)) : 반환값을 설정
will( this>returnArgument(this->returnArgument(num)) : 메서드에 전달된 $num 번째 인수를 반환 값으로 설정
will( $this->returnSelf()) : 반환 값에 모의객체 자체를 설정
will( $this->throwException(예외)) : 반환값에 예외값를 설정
등으로 사용하는데 좀더 사용하기 편리하도록 함수화 한것들도 있다.

willReturn : 특정 값을 반환하도록 설정

$mock->method('methodName')->willReturn('value');

willReturnMap :인자 값에 따라 다른 반환 값을 설정

$map = [
    ['param1', 'result1'],
    ['param2', 'result2']
];
$mock->method('methodName')->willReturnMap($map);

willReturnArgument : 특정 인자 값을 그대로 반환하도록 설정

$mock->method('methodName')->willReturnArgument(0); // 첫 번째 인자 값을 반환

willReturnCallback : 콜백 함수를 사용하여 반환 값을 동적으로 설정

$mock->method('methodName')->willReturnCallback(function ($arg) {
    return $arg . ' processed';
});

willReturnSelf
설명: 호출된 객체 자체를 반환하도록 설정. 메서드 체이닝에 유용.

$mock->method('methodName')->willReturnSelf();

willReturnOnConsecutiveCalls 설명: 메서드가 연속적으로 호출될 때 각각 다른 값을 반환하도록 설정

$mock->method('methodName')
     ->willReturnOnConsecutiveCalls('first', 'second', 'third');

Assert

테스트 대상 함수가 예상결과와 같은지 판단하는 함수가 Assert 함수이다. 테스트 대상 함수의 결과치가 여러가지 인 만큼 Assert 도 다양하다.

  • assertEquals() : 값이 같은지 확인. assertEquals(5, $actual);
  • assertSame() : 값과 형식도 같은지 확인. assertSame(5, $actual);
  • assertNotSame() : 값과 형식도 다른지 확인. assertNotSame(5, $actual);
  • assertTrue() / assertFalse() : 불린값이 같은지 확인. assertTrue($actual);
  • assertInstanceOf() : 인스턴스가 같은지 확인. assertInstanceOf(Foo::class, $actual);
  • assertNull(): 값이 null 인지 확인 assertNull($actual);
  • assertNotNull(): 값이 null 이 아닌지 확인 assertNotNull($actual);
  • assertStringStartsWith() / assertStringEndsWith() / assertStringContainsString() : 값의 문자열중에 시작문자, 종료문자, 포함문자가 있는지 확인. assertStringStartsWith(‘foo’, $actual);
  • assertCount() : 배열인수의 개수가 같은지 확인. assertCount(1, $actual)
  • assertEmpty() : 배열이 빈 배열인지 확인. assertEmpty($actual)
  • assertContains() : 배열에 해당 아이템이 포함되었는지 확인. assertContains(‘foo’, $actual)

Laravel에서는 아래와 같은 Assert 도 추가되어 있다.

  • assertPageLoaded($uri, $message = null) :최신 페이지가 로드되었는지 확인한다… URL/메시지가 존재하지 않으면 오류가 보고된다.
  • assertResponseOk() : 페이지 응답이 정상인지 확인
  • assertReponseStatus($code): 지정된 코드에 응답한지 여부
  • assertViewHas($key, $value = null): 지정된 데이터가 뷰에 존재하는지 여부
  • assertViewHasAll($bindings):지정된 데이터 계열이 뷰에 존재하는지 여부
  • assertViewMissing($key):이 데이터가 뷰에 존재하지 않는지 여부를 지정
  • assertRedirectedTo($uri, $with = []):지정된 URI로 리디렉션할지 확인
  • assertRedirectedToRoute($name, $parameters = [], $with = []):클라이언트가 지정된 경로로 리디렉션되는지 여부
  • assertRedirectedToAction($name, $parameters = [], $with = []):작업으로 리디렉션할지 여부
  • assertSessionHas($key, $value = null):세션에 키/값이 존재하는지 여부
  • assertSessionHasAll($bindings):지정된 kv가 세션에 존재하는지 여부
  • assertSessionHasErrors($bindings = []):세션에 오류가 있는 걸까요?
  • assertHasOldInput():세션에 이전 데이터가 있는지 여부
    등등 많이 있다.

테스트 함수의 맨 마지막에 적어준다.
실패의 경우에는 예상치과 실제값이 어떻게 다른가 표시해준다.

  $this->assertEquals('foo with different result', $result);

어노테이션 기능

PHPUnit 에서는 여러가지 어노테이션을 제공해서 테스트 함수에 주석으로 표기하면 PHPUnit이 읽어들여 적절한 액션을 취해준다.

@depends : 현재 테스트가 다른 테스트의 결과를 받아서 처리해야할 필요가 있을때 지정한다.

class ExampleTest extends TestCase
{
   public function testEmpty()
   {
       $stack = array();
       $this->assertEmpty($stack);
       return $stack;
   }
   /**
    * @depends testEmpty
    */
   public function testPush(array $stack)
   {
       array_push($stack, 'foo');
       $this->assertEquals('foo', $stack[count($stack)-1]);
       $this->assertNotEmpty($stack);
       return $stack;
   }
   /**
    * @depends testPush
    */
   public function testPop(array $stack)
   {
       $this->assertEquals('foo', array_pop($stack));
       $this->assertEmpty($stack);
   }
}

@depends 의 대상이 되는 함수에는 리턴값이 있고 ,이를 depends하는 함수가 인수로서 받아서 테스트에 이용하는 방식이다.

@dataProvider : 테스트 대상 함수에 인수를 전달 하는 것이 가능하다. 테스트 함수에 인수를 다양하게 지정하여 테스트 대상함수가 해당 인수로 성공하는 지 알아 볼때 편리하다.

class ExampleTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAppend($a, $b, $expected)
    {
        $result = $a . $b;
        $this->assertEquals($expected, $result);
    }

    public function additionProvider()
    {
        return [
            ['Hello, ', 'World!', 'Hello, World!'],
            ['This ', 'is a ', 'This is a '],
            ['PHP ', 'unit test', 'PHP unit test'],
        ];
    }
}

@testWith : dataProvider와 비슷하지만 좀더 간단하게 지정할수 있다.

class ExampleTest extends TestCase
{
    /**
     * @testWith ["Hello", "World", "HelloWorld"]
     *           ["This", " is a ", "This is a "]
     *           ["PHP", " unit test", "PHP unit test"]
     */
    public function testConcatenateStrings($a, $b, $expected)
    {
        $result = $a . $b;
        $this->assertEquals($expected, $result);
    }
}

@group : 그룹명을 지정하여 테스트 시에 선택적으로 해당 그룹만 실행할수있다.

 /**  
 * @group mytest  
 */
 public function test_1(){}
 
 public function test_2(){}
 
 /**  
 * @group mytest  
 */
 public function test_3(){}

php artisan test --group mytest 을 실행하면 test_1(), test_3() 만 실행된다.

Laravel : ServiceProvider & Service Container

Laravel : ServiceProvider & Service Container

Laravel : Service Provider & Container

공식문서에서 정의한 내용을 보자.
https://laravel.kr/docs/8.x/providers

쉽게 가보자.
서비스 컨테이너와 서비스 프로바이더에서 서비스라는 단어를 빼고 생각해 보자.

컨테이너와 프로바이더의 관계가 된다.
컨테이너는 말 그대로 뭔가 들어있는 우리가 항만에서 볼수있는 그 거대한 컨테이너박스를 의미한다.
프로바이더는 제공자라는 의미로 뭔가를 공급하는 사람을 뜻한다.

우리가 만드는 어플리케이션을 하나의 생산품이라고 생각해보자. 이때 우리는 자주 사용하는 부품이 여러 공정에서 필요한데 이를 매번, 매공정마다 담당자가 제조사에 전화해서 달라고 하면 담당자마다 다른 곳에 전화하는 실수를 한다든지 잘못 주문한다든지 하게된다.
이럴때 우리는 프로바이더에게 해당 부품의 공급을 의뢰해서 컨테이너 단위로 물건을 요청할수있다. 이렇게 하면 프로바이더에게 요청하는 담당자를 통해서 부품을 공급받게되어 빠르고 안정적으로 제품을 생산할수 있는 환경이 된다.

서비스 컨테이너와 서비스 프로바이더도 같은 원리이다.

Laravel에서 Service Provider 는 애플리케이션이 기동되는 순간(이를 라라벨에서는 부트스트래핑이라고 부른다.) 필요한 프로바이더들을 등록하고 프로바이더가 무엇을 제공할건지 서비스 컨테이너 내용을 등록하면 우리가 개발하는 프로그램의 어디에서든지 자유롭게 컨테이너의 내용을 꺼내서 활용할수 있도록 해준다.

Service Provider

서비스 프로바이더(서비스 공급자) 는 위에서 설명했듯이 제품을 공급해줄 담당자를 지정한다.

config/app.php 에 보면 providers 항목이 있고, Laravel 프레임워크에서 필요한 프로바이더와 애플리케이션에서 필요한 프로바이더를 미리 등록해 놓은 것이 보인다.
물론 우리가 만든 프로바이더도 등록이 가능하다.

추가하는 provider 는 ServiceProvider 를 상속받는데 register 와 boot 함수를 오버라이드 한다.

register : 객체를 서비스 컨테이너에 바인딩(연결하다) 한다.
boot : laravel이 부트스트래핑할때 호출된다.

여기서 착각하면 안되는 것이, 각 프로바이더마다 register->boot 순으로 실행되는게 아니라, 모든 프로바이더의 register가 종료된후에 모든 프로바이더의 boot 가 실행된다는 것이다.

register()

register 함수에서는 외부에서 프로바이더에게 요청시에 대답해줄 컨테이너정보를 등록한다. 즉, 컨테이너를 가지는게 아니라 보내줄 컨테이너를 지정한다.
컨테이너를 연결할때, singleton 과 bind, instance, when, tag 등의 연결방법이 있다.

singleton : 말그대로 외부에서 여러번 요청해도 하나의 똑같은 컨테이너만 돌려준다.
bind : 컨테이너 요청때마다 새로운 컨테이너 만들어서 돌려준다.
instance : 간단하게 컨테이너를 등록할수있다. 테스트코드 작성시에 Mock용 컨테이너 등록시에 유용하다.
when : 의존관계에서 다른 의존관계의 컨테이너가 필요할때 사용한다.
tag : 여러개를 그룹화 해서 하나의 컨테이너 키이름으로 불릴수있게 해준다.

1.make 로 나만의 프로바이더를 만든다.

php artisan make:provider MyProductServiceProvider

2.생성된 /app/Providers/MyProductServiceProvider.php 가 생성되었고 register()/boot() 빈함수 두개가 있다.

public function register()  
{  
    $this->app->singleton('myutility', function($app){  
       return new MyUtility();   
    });  
}

3.MyUtility 클래스가 없으므로 app/MyUtils/MyUtility.php 를 폴더생성과 함께 만들자.

class MyUtility  
{  
    public function exampleMethod($value): string  
  {  
        return strtoupper($value);  
    }        
}

4.config/app.php 파일에 우리가 만든 프로바이더도 등록해보자.

confit/app.php 
'providers' => [
....
App\Providers\MyProductServiceProvider::class,
]

5.프로바이더를 통해서 컨테이너가 등록이 되었고 어디서든지app(‘myutility’)라는 호출명령어로 MyUtility가 불려지게 된다. 컨트롤러에서 사용해보자.

class ExampleController extends Controller  
{  
    protected MyUtility $myUtility;  
  
    public function __construct()  
    {  
        $this->myUtility = app('myutility');  
    }  
  
    public function index()  
    {  
        $value = 'hello world';  
        return $this->myUtility->exampleMethod($value); // "HELLO WORLD" 반환  
  }  
}

6.라우터에 등록해서 확인해 보면 대문자로 HELLO WORLD 가 출력되는것을 확인할수 있다.

boot()

boot 는 다른 프로바이더의 register가 모두 끝난후에 호출된다.

1.위에서 만들었던 MyproductServiceProvider.php 의 boot 함수에 내용을 추가해보자.

public function boot()  
{  
    //  
  view()->composer('welcome', function($view){  
        return $view->with('helloworld', app('myutility')->exampleMethod('hey! there'));  
    });       
}

2.welcome.blade.php 에서 helloworld 변수를 사용해보자 .

<h1> {{ $helloworld }} </h1>

3.뷰화면에 hey! there 가 출력된다.

Service Provider implements DeferrableProvider

deferred. 말그대로 지연 시킨다는 건데 무엇을 지연시키느냐면 register 를 지연 시킨다는 말이다.
부트스트래핑과저에서 모든 프로바이더에 대해 register에서 사용할 컨테이너를 만들 준비를 해둔다. 이렇게 하면 프로바이더가 많아질수록 애플리케이션 초기기동이 늦어질수있는데, 프로바이더 에 요청할때 비로소 컨테이너만들 준비를 하도록 일단 지연 시키는게 deferrer 이다.

기존 서비스 프로바이더 선언에 DeferrableProvider 인터페이스를 추가하고 provides 함수를 오버라이드해서 사용할 컨테이너를 적으면 된다.

class MyProductServiceProvider extends ServiceProvider  implements  DeferrableProvider


public  function  provides()
{
return  [MyUtility::class];
}

사용할때는 동일하다.

Facade

서비스 프로바이더를 사용해는 것도 좋지만 app()함수를 통해 만드는것은 더 직관적이라 어떤 서비스 프로바이더인지 알아보기 어려울수도 있다.
Facade(겉으로, 표면적인) 으로 등록하면 서비스 프로바이더 호출 이름만으로도 쉽게 알아볼수 있게 해준다.

1.Facade파일을 생성하자. getFacadeAccessor 함수에 위의 과정에서 등록했던 ‘myutility’ 키 이름을 지정하면 자동으로 서비스 프로바이더를 불러준다.

class MyUtilityFacade extends Facade  
    {  
    protected static function getFacadeAccessor()  
    {  
        return 'myutility'; // 서비스 컨테이너에 등록된 키  
  }  
}

2.config/app.php 에 aliases 로 등록 하자.

'MyUtilityFacade' => App\Providers\MyProductServiceProvider::class,

3.컨트롤러에서 적었던 서비스 프로바이드 관련 정보를 삭제하고, MyUtirilityFacade 를 이용해 보자.

use App\Facades\MyUtilityFacade;  
  
class ExampleController extends Controller  
{  
  
    public function index()  
    {  
        $value = 'Hello World';  
        return MyUtilityFacade::exampleMethod($value); // "HELLO WORLD" 반환  
  }  
}

Laravel : Router 에 대해

Laravel : Router 에 대해

Laravel : Route 에 대해 알아보기

Laravel 프레임워크에서는 사용자의 Http 요청에 대해 Http Kernel 을 통해서 Router에 전달하여 컨트롤러와 뷰의 결과를 돌려주는 역활을 한다.

router 는 각 요청에 맞게 분류해서 web 또는 api , sonsole, channel 등 요청형식에 맞는 미들웨어를 거치게 되는데, Laravel의 routes 폴더에 보면 각 요청에 맞게 routing 을 담당하는 4개의 기본 파일들을 볼수있다.

  • web.php : HTTP 요청을 받아서 화면에 표시할때 사용되어진다. laravel 프로그램워크에서는 web.php에 csrf(교차 사이트 요청 위조 공격) 미들웨어가 처리하고 있어서, csrf키가 없이는 외부에서 post, put, delete 등을 할수 없다. 즉, 서버에서 생성한 csrf 키를 이용해서 post,put,delete시에 _token 이라는 이름으로 값은 키값을 전송하지 않으면 등록 거부가 된다.
  • api.php : 외부에서 POST, PUT 등이 가능하며 요청주소에 /api/ 가 포함되어 있을때 api.php 에서 처리하게 된다.

Laravel 프레이워크 중 /app/Http/Kernel.php 파일을 열어보면 아래와 같이 web 와 api 일때 처리하는 미들웨어가 다르다는 것을 알수있따.

  /**
     * The application's route middleware groups.
     *
     * @var array<string, array<int, class-string|string>>
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

routes/api.php 파일을 수정한 후에는 반드시 php artisan route:cache 해주자.

web route 과 api route 를 확인해 보자.

web route 부터 확인해 보자.

1.먼저 Route 에서 해당 주소로 요청 했을 때, 처리할 Controller 를 추가하자.

php artisan make:controller WebRouteController

2.아래처럼 화면에 보여질 내용을 추가하자.

public function test(Request $request) {
return ‘welcome wep route’
}

3.web.php 에 아래와 같이 과정1과 2에서 만든 Controller 와 함수를 지정한다.

Route::get('webroute', 'WebRouteController@test');

4.localhost/webroute 로 접속해 보면 ‘welcome wep route’ 가 보일것이다.
만일 “Target class [WebRouteController] does not exist.” 메시지가 나타난다면, 컨트롤러의 전체 주소를 적어줘도 된다.

Route::get('webroute', 'App\Http\Controllers\WebRouteController@test');

만일 Router 에서 매번 App\Http\Controllers\ 의 긴 경로를 적기 귀찮다면 app/Providers/RouteServiceProvider.php 의 아래 항목을 주석제거 하면 된다.

// protected $namespace = 'App\\Http\\Controllers';
를
protected $namespace = 'App\\Http\\Controllers';
로 주석제거

이제 과정3에서 처럼 적어줘도 에러없이 메시지가 출력된다.

api route 도 확인해 보자.

api route 는 앞서 말했듯이, post, get, put, delete 메소드에 csrf 가 적용되어 있지않기 때문에 주소만 알면 전부 다 가능하다. post가 되는지 확인해 보자.

1.먼저 Route 에서 해당 주소로 post 했을 때, 처리할 Controller 를 추가하자.

php artisan make:controller ApiRouterController

2.post로 전송받는거기 때문에 받은 내용을 그대로 출력해보자

  function postTest(Request $request)
  {
    return $request;
  }

3.api route에 접속주소와 컨트롤러,함수를 지정해보자

Route::post('/posttest', [App\Http\Controllers\ApiRouterController::class, 'postTest']);

4.주소로 post 메소드를 이용해서 데이터를 보내보자. (PostMan 또는 curl 이용한다.)

curl -X POST -H "Content-Type: application/json" -d '{"haha":"oh"}' http://localhost:80/api/posttest

Laravel에서는 post 값을 json 을 기본으로 하고있다.
일반 text를 전송하고자 한다면 아래처럼 postTest의 내용을 바꾸고

function postTest(Request $request)  
{  
    return response($request->getContent(), 200)  
        ->header('Content-Type', 'text/plain');  
}

curl -X POST -H "Content-Type: text/plain" -d 'poooooost' http://localhost:80/api/posttest

resource route 로 CRUD(create, red, update, delete) 를 쉽게 해보자.

api route 로 post, put, delete을 각각 명시해줘도 좋지만 어차피 get,post,put,delete 를 구현할꺼라면 CRUD 에 직접대응하는 resource route 이용해 보는것도 좋다.

1.먼저 Controller 를 추가하는데 맨뒤에 인자로 --resource를 붙여준다.

   php artisan make:controller ResourceRouteSampleController --resource

2.ResourceRouteSampleController.php를 열어보면 index,create, store, show edit 함수가 미리 정의되어 있다. 이말은 즉, 사용자가 보내는 요청 메소드에 따라서 laravel의 resource라우터가 해당 함수를 실행한다는 의미이다.

   php artisan make:controller ResourceRouteSampleController --api

아래 처럼 생성된다.

  <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    
    class ResourceRouteSampleController extends Controller  
{  
    /**  
 * Display a listing of the resource. * * @return \Illuminate\Http\Response  
 */  
 public function index()  
    {  
        //  
       return response()->make('my message' , 405);
  }  
  
    /**  
 * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request  
  * @return \Illuminate\Http\Response  
 */  
 public function store(Request $request)  
    {  
        //  
  }  
  
    /**  
 * Display the specified resource. * * @param int $id  
  * @return \Illuminate\Http\Response  
 */  
 public function show($id)  
    {  
        // 
        return response()->make($id , 405); 
  }  
  
    /**  
 * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request  
  * @param int $id  
  * @return \Illuminate\Http\Response  
 */  
 public function update(Request $request, $id)  
    {  
        //  
  }  
  
    /**  
 * Remove the specified resource from storage. * * @param int $id  
  * @return \Illuminate\Http\Response  
 */  public function destroy($id)  
    {  
        //  
  }  
}

과정4에서 테스트하기 위해서 함수에 응답을 추가했다.
index() 함수 : return response()->make(‘my message’ , 405);
show() 함수 : return response()->make($id , 405);

3.이제 api.php 에 resource route를 추가하면 된다.
Route::apiResource(’/resources’,‘ResourceRouteSampleController’);

만일 사용자로부터 일부 메소드만 허용하고자 한다면 only 키워드를 사용할수있다.

Route::apiResource('/resources','ResourceRouteSampleController', ['only' => ['index','store', 'show']]);
  • 참고로 Route::resource() 도 있는데, 이는 일반적인 리소스에 대한 것으로 create, edit 등이 추가된 것이다.
  • 일반적으로 RESTful API 에는 create, edit 메소드가 없는것이 정상이므로 Route::apiResource 를 사용한다.
  1. 테스트 해보자
    curl -X GET http://localhost:80/api/resources -> message
    curl -X GET http://localhost:80/api/resources/1 -> 1

Laravel : Middleware

Laravel : Middleware

Laravel : Middleware

middleware 란, 말 그대로 중간에 걸쳐서 뭔가 처리를 추가하는 소프트웨어(또는 하드웨어) 를 말한다.
Laravel에서의 미들웨어는 HTTP요청에 대해 추가처리를 하기 위한 프로그램을 말한다.

Laravel 프레임워크의 app/Http/Middleware 에 보면, 쿠키암호화, 인증, csrf 처리등 사용자 요청이 내부에 전달되기 전에 한번더 작업을 추가하는 역활을 하는 프로그램 들이 있다.

미들웨어는 요청의 흐름에 따라 두가지로 나뉜다.
1.요청(request)를 받아서 처리하고 다음으로 진행을 할지를 결정
2.요청을 전달하여 컨트롤러 등에서 처리한 결과(response)에 대해 처리를 하고 응답(response)를 전달

미들웨어를 적용할때는 라이터에서 적용하는 방법과 컨트롤러에서 적용하는 방법이 있다.

http 요청(request)값을 넘기기 전의 Middleware

공식문서처럼 CheckAge 라는 미들웨어를 추가해보자.
이 미들웨어는 200살 이하는 home 으로 이동시키는 역활을 한다.

1.middleware 를 추가하자.

php artisan make:middleware CheckAge

2.CheckAge.php의 handle 함수에 나이체크하고 home으로 보내는 함수를 추가해보자.

 if ($request->age <= 200) {
        return redirect('/');
    }

3.Kernel.php 에 생성한 미들웨어를 추가해준다. 우리가 만든 미들웨어는 전역(모든 http요청)에서 사용할 용도가 아니기에 어플리케이션 미들웨어로 등록한다.

protected $routeMiddleware = [  
 ...
  
    'checkAge' =>  \App\Http\Middleware\CheckAge::class,  
];

4.Router설명할때 만들었던 /webroute 에 checkAge미들웨어를 추가해보자. routes/web.php 를 수정하자

Route::get('/webroute', 'WebRouteController@test')->middleware('checkAge');

또는 아래처럼 그룹형식으로 지정하여 배열인수에 다른 것들도 같이 추가하거나, 여러개의 route를 묶어서 미들웨어를 지정할수도 있다.

Route::middleware(['checkAge'])->group(function () {  
    Route::get('/webroute', 'WebRouteController@test');  
});

5.이제 route 를 갱신하기 위해서 php artisan route:cache 를 실행하고, 접속해 본다.

  • localhost/webroute?age=123 일때는 ‘/’ 로 이동한다.
  • localhost/webroute?age=201 일때는 화면에 web route 문자가 출력된채로 멈춰있다.(미들웨어 조건에서 200 이상일때는 요청된 페이지를 그대로 표시한다.)

http 응답(response)값을 넘기기 전의 Middleware

앞서 설명했듯이 요청(middleware)->서버에서처리->응답(middleware) 이렇게 서버에서 처리하기 전후로 미들웨어로 뭔가 추가처리가 가능하다.
이번에는 응답정보를 미들웨어로 처리해보자.
1.미들웨어를 추가하자.

php artisan make:middleware CheckAgeResponse

2.CheckAgeResponse.php의 handle 함수에 http응답의 푸터를 추가하자.

public function handle(Request $request, Closure $next)  
{  
    $response = $next($request);  
  
    $content = $response->getContent();  
    $modifiedContent = $content . '<!-- Custom Footer -->';  
    $response->setContent($modifiedContent);  
  
    return $response;  
}

3.어플리케이션 미들웨어로 등록한다.

'checkAgeResponse' =>  \App\Http\Middleware\CheckAgeResponse::class,

4.Router설명할때 만들었던 /webroute 에 checkAge미들웨어를 추가해보자. 위에서 추가한 checkAge 옆에 추가하자.

Route::middleware(['checkAge','checkAgeResponse'])->group(function () {  
Route::get('/webroute', 'WebRouteController@test');  

});

5.이제 route 를 갱신하기 위해서 php artisan route:cache 를 실행하고, 접속해 본다.
http응답이기 때문에 curl로 확인해 보자

 curl -v   http://localhost:80/webroute?age=123

응답내용의 맨 마지막에 미들웨어에서 추가한 '!-- Custom Footer -->'가 표시된다.

Laravel : env , config 알아보기

Laravel : env , config 알아보기

Laravel 프레임워크의 전역 환경설정 관련 항목인 .env 파일과 config 디렉토리

Laravel 프레임워크는 애플리케이션을 읽을때, 먼저 config 폴더에 있는 파일들을 읽어들인다. 이때 config 하위의 각 설정파일(app.php, cache.php)등에서는 자체의 설정값과 env() 함수를 통해서 얻어진 설정값을 이용하여 각 항목별로 환경설정을 한다.
왜 이렇게 이중처리를 할까? 생각하게되는데, 애플리케이션 개발시에 app-dev, app-product, app-qa 등 여러상황에 따라 설정값이 달라지도록 해서 각 설정에 맞는 env 파일을 이용하도록 하기 위해서이다.
즉, .env-app-dev 라는 파일을 이용해서 기동하면 개발용 설정변수가 할당되고 .env-app-product 라는 설정파일을 이용하면 실제 서비스용 설정변수가 적용되도록 할수있다는 것이다.

주의할점은 env()함수는 config폴더안의 파일에서만 참조하도록 한다. Laravel에서는 config:cache를 통해 설정내용이 캐싱되면 config하위에 있는 파일들이 정의한 값만 가지게 되므로 env설정파일에 있는 값은 불러들이지 않기 때문에 null 이 되어버리기 때문이다.

실제 운영에서의 예를 들어보자

개발서버 : .env.dev 이름으로 저장
스페이지서버 : .env.stg 이름으로 저장
프로덕션서버 : .env.prod 이름으로 저장

이렇게 3개의 환경이 있어서, 각각 데이터베이스, 인증환경등이 다르다고 생각해보자. 아니, 실제로 다르다.

각각의 서버의 nginx의 환경설정(nginx/fastcgi_params)에 fastcgi_param 에 APP_ENV 라는 파라메터를 전달하는 것으로 각각의 환경을 구분할수있다.

개발서버의 nginx/fastcgi_params 에 추가
fastcgi_param APP_ENV dev;

스테이지서버의 nginx/fastcgi_params 에 추가
fastcgi_param APP_ENV stg;

프로덕션서버의 nginx/fastcgi_params 에 추가
fastcgi_param APP_ENV prod;

이제 프로그램 각 서버의 nginx를 기동하면 각각의 설정(dev, stg, prod)에 맞는 환경설정을 자동으로 읽어낸다.

.env 파일의 구성에 대해 알아보자

.env 파일은 앱이름, 데이터베이스 또는 다른 외부 시스템 접속정보 등 다소 민감한 정보가 들어있을수 있다. 따라서 외부에 공개하는 경우에는 .env 파일을 .gitingnore 파일에 추가해서 외부에 노출되지 않도록 할 필요가 있다.

아래는 .env 에 대한 간단한 항목설명이다.

APP_NAME=Laravel 		# 애플리케이션 이름
APP_ENV=local 			# 구동 환경(dev, stage, production 등)
APP_KEY=			# 애플리케이션의 암호화 키를 설정합니다. 이 키는 보안에 중요한 역할을 하며, 32자 이상의 무작위 문자열이어야 한다.
APP_DEBUG=true 			# 디버그 모드를 활성화할지 여부를 설정합니다. true로 설정하면 자세한 오류 메시지가 표시되며, false로 설정하면 오류 메시지가 숨겨진다
APP_URL=http://localhost 	# 사이트 도메인 주소 설정한다.

LOG_CHANNEL=stack 		# LOG_CHANNEL 변수는 config/logging.php 파일에서 정의된 로깅 채널 중 하나를 지정합니다. 기본적으로 Laravel은 여러 가지 채널을 미리 정의해두고 있습니다. 이들 중 하나를 선택하거나 필요에 따라 커스텀 채널을 정의할 수 있습니다.
LOG_LEVEL=error # 기록될 로그의 최소심각도 수준을 지정한다. error, debug, info, notice 등이 있다.

이하 DB,REDIS, AWS 등 항목이 있다.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

.env 파일에 등록된 값은 config 를 통해서 얻어오는게 좋다.(앞서 얘기했듯이…)

 $environment = config('app.env');

Blade템플릿에서는 아래와 같이 사용한다.

@if (config('app.env') === 'local')
    <p>This is the local environment.</p>
@else
    <p>This is not the local environment.</p>
@endif

config 폴더의 구성에 대해 알아보자

config 폴더하위의 파일들은 bootstrap의 LoadConfiguration.php 에서 읽어들여 메모리에 키-값 형태로 저장된다.
사용자 정의 환경변수를 추가하고 싶다면
config/myval/hehe/omg.php 을 아래와 같이 만든다.

<?php

return [
	'hello' => 'hello. my value',
];

이이렇게 저장했다면, config(‘myval.hehe.hello’) 로 불러들일수 있다.

2024년 6월 16일 일요일

WebRTC : WebRTC란?

WebRTC : WebRTC란?

WebRTC란?

공식 웹사이트 : https://webrtc.github.io/webrtc-org/architecture/

WebRTC(Web Real-Time Communication)는 Google이 2010년 6,820만 달러에 인수하여 "WebRTC"로 이름을 바꾼 VoIP 소프트웨어 개발업체 Global IP Solutions의 GIPS 엔진으로, 인터넷 브라우저용 오디오, 비디오 및 데이터의 실시간 통신 플랫폼을 구축하기 위해 2011년에 오픈 소스화되었다.

그렇다면 WebRTC는 무엇을 할 수 있나? 주로 의료 분야의 온라인 상담/원격 진료/원격 상담 외에도 전자상거래 대화형 라이브 방송 솔루션과 교육 산업 솔루션이 더욱 인기를 얻고 있다. 또한 클라우드 게임에서도 사용해서 저사양핸드폰이나 컴퓨터에서 고사양게임을 즐길수 있도록 하는 서비스도 있다.

WebRTC 아키텍처

아래 그림은 WebRTC 공식 웹사이트 에 있는 WebRTC의 전체 아키텍처 다이어그램 이다.

그림에서 보듯이 WebAPI 를 통해서 브라우져에 WebRTC가 내장되어있다면 누구라도 web 에서 브라우져를 통해서 영상,오디오 통신을 할수 있다.

WebRTC를 사용하는 이유?

WebRTC 가 인기를 얻는 데에는 여러 가지 이유가 있다. 그 이유 중 일부는 다음과 같다.

  • WebRT* 는 플러그인이 필요 없는 최신 실시간 통신 기술이다. 오디오, 비디오 스트리밍 및 데이터 공유를 위해 추가 플러그인이나 애플리케이션이 필요하지 않다. Javascript , API(응용 프로그래밍 인터페이스) 및 HTML5를 사용하여 브라우저에 통신 기술을 내장한다 . Google Hangouts , Facebook Messenger , ZOOM , Zendesk Customer Support, Skype for Web 등과 같은 제품은 모두 WebRTC기반으로 운영된다.
  • 브라우저는 P2P 방식으로 다른 브라우저와 직접 실시간 미디어를 교환할 수 있다.
  • 타사 소프트웨어 없이도 다른 다양한 스트리밍 시스템보다 높은 수준의 보안을 제공한다.
  • 실시간 통신을 위한 별도의 플러그인이 필요하지 않다.

WebRTC는 무엇을 할 수 있나?

  • 온라인 채팅방
  • 온라인 영상 채팅
  • 원격 실시간 모니터링
  • 화면 공유
  • 대용량 파일의 지점간 전송
  • 실시간 게임
  • 라이브 스트리밍
  • . . .
  • 실시간이 필요한 기타 애플리케이션 시나리오

WebRTC의 장점

  • 플랫폼 및 장치 독립성. 개발자는 단말 및 운영체제 수준의 호환성 문제에 대한 걱정 없이 WebRTC를 지원하는 브라우저를 통해 WebRTC 기반의 다양한 애플리케이션을 개발할 수 있다. 또한 WebRTC는 플랫폼 호환성 문제를 방지하기 위해 표준 API(W3C)와 표준 프로토콜 지원(IETF)도 제공한다.
  • 음성 및 영상의 보안 처리, WebRTC는 SRTP를 통해 음성 및 영상을 암호화한다. 브라우저를 사용하여 로그인하여 음성 및 영상에 액세스하는 사용자는 사용자 시나리오의 보안 요구 사항(예: 보안되지 않은 Wi-Fi 환경의 음성 및 영상)을 충족하는 비교적 안전한 설정이 필요하며 다른 사람은 이를 모니터링할 수 없다.
  • 고급 언어 및 비디오 처리를 지원하고 WebRTC는 최신 인코딩을 지원하며 음성은 Opus를 지원하고 비디오는 VP8을 지원한다. 내장된 인코딩은 다른 타사 다운로드의 보안 위험을 제거하고 더 나은 음성 또는 비디오 품질을 달성하기 위해 네트워크 환경 조정을 지원할 수 있다.
  • 안정적인 전송 생성을 지원하는 WebRTC는 NAT 환경에서도 얻을 수 있는 전송 안정성을 포함하여 안정적인 전송 방법을 제공한다.
  • 멀티미디어 스트림 처리를 지원하는 WebRTC는 멀티미디어와 다중 리소스의 집합을 제공하고 RTP 및 SDP의 확장을 제공한다.
  • 다양한 네트워크 환경 조정을 지원한다. WebRTC는 네트워크 플랫폼에서 실행되기 때문에 네트워크 환경과 대역폭에 매우 민감한다. 네트워크 혼잡을 피하기 위해 네트워크 환경과 대역폭 요구 사항을 스스로 감지하고 조정할 수 있다. RTCP 및 SAVPF를 통해 이 기능을 보장한다.
  • WebRTC는 VoIP 음성 및 비디오와 잘 호환되며 SIP, Jingle 및 XMPP 도킹을 포함한 다른 미디어와의 호환성 작업을 구현한다. 동시에 다른 기존 프로토콜과 인터페이스해야 하는 경우 WebRTC 게이트웨이를 사용하여 원활한 호환성을 달성하고 기존 프로토콜과의 호환성을 보장할 수 있다.

WebRTC는 브라우저에서 어떻게 작동하나?

WebRTC는 기본적으로 브라우저를 통해 웹을 통한 실시간 통신이다. 브라우저 간의 통신을 허용한다. WebRTC 웹 애플리케이션은 HTML 과 *
JavaScript 의 혼합 으로 프로그래밍된다. 사용자는 CSS를 사용하여 사용자 정의 할 수도 있다 . 표준화된 WebRTC API를 통해 웹 브라우저 와 작동하고 통신한다. 따라서 WebRTC API는 일련의 유틸리티를 제공 한다. 그 중 일부는 연결 관리(P2P 방식), 인코딩/디코딩 기능 협상, 선택 및 제어, 미디어 제어, 방화벽 설정 등과 같다.

WebRTC는 사용자 정의가 가능하기 때문에 구현 범위가 넓다. WebRTC 의 기능은 세 부분으로 나눌 수 있다.

  1. MediaStream : 첫 번째 단계는 사용자가 공유하려는 데이터를 가져오는 것이다. 이 경우 사용자가 원하는 스트림(오디오/비디오)을 캡쳐하여 통신 패턴을 설정한다. 로컬 미디어 스트리밍을 사용하면 브라우저가 카메라 및 웹 마이크와 같은 스트리밍 장치에 액세스할 수 있다. 또한 브라우저가 미디어를 캡처할 수도 있다. 사용자는 브라우저 기능을 활용하여 getUserMedia()액세스 권한을 얻을 수 있다.
  2. RTCPeerConnection : 사용자가 통신 흐름을 결정하고 나면 다음 단계는 이를 원격 서비스 시스템에 연결하는 것이다. 이를 통해 당사의 브라우저는 음성 및 영상 통화를 위해 원격 서비스 브라우저(피어)와 직접 데이터를 교환할 수 있다. STUN 및 TURN 서버를 통해 발신자와 수신자 간의 상관관계를 허용한다 .
  3. RTCDataChannel : 브라우저가 지점 간 양방향으로 데이터를 교환할 수 있도록 한다.

시그널링이란 ?

WebRTC는 RTCPeerConnection을 사용하여 브라우저 간(종단간)에 스트리밍 데이터를 전송하지만 통신을 조정하고 제어 메시지를 보내는 메커니즘(시그널링이라는 프로세스)도 필요한다. WebRTC는 신호 방법과 프로토콜을 지정하지 않다.

STUN과 TURN이 무엇인가?

WebRTC는 P2P로 작동하도록 설계되었으므로 사용자는 가장 직접적인 경로를 통해 연결할 수 있다. 그러나 WebRTC는 실제 네트워킹을 처리하도록 구축되었다. 클라이언트 애플리케이션은 NAT 게이트웨이와 방화벽을 통과해야 하며 P2P 네트워크는 직접 연결이 실패할 경우 대체해야 한다. 이 과정에서 WebRTC API는 STUN 서버를 사용하여 컴퓨터의 IP 주소를 획득하고 지점 간 통신이 실패하는 경우 TURN 서버를 중계 서버로 사용한다.
즉, STUN은 클라이언트가 자신의 공용 IP와 포트를 알아내는 프로토콜이고, TURN은 클라이언트 간의 직접적인 연결이 불가능할 때 중계 서버를 통해 데이터를 전달하는 프로토콜이다. ( 그렇다고 서버를 통해 통신을 하는건 아니고 기본적으로 개인대 개인이 통신하는 것이다.)

WebRTC는 통신은 안전한가?

모든 WebRTC 구성 요소는 암호화되어야 하며 보안 소스( 또는 localhost) JavaScript API에서만 사용해야 한다 . HTTPS신호 메커니즘은 WebRTC 표준에 의해 정의 되지 않으므로 보안 프로토콜을 사용해야 한다.
WebRTC는 고급 오디오 및 비디오 코덱(Opus 및 VP8/9), 필수 암호화 프로토콜(SRTP 및 DTLS) 및 네트워크 주소 변환기(ICE 및 STUN)를 포함한 고급 실시간 통신 기술을 결합한다.

WebRTC 구글 예제

https://github.com/webrtc/samples 에 구글이 제공해 주는 표준 예제가 있다.
git clone 도는 다운로드 해서 npm install & npm start 하면 WebRTC samples 화면이 나오는데 테스트 하고 자 하는 링크를 클릭하면 된다.

WebRTC 기본개념

WebRTC는 WebComponent와 마찬가지로 기술 세트이며 API는 세 부분으로 구성된다.

  • getUserMedia : 로컬 미디어 리소스(카메라, 마이크) 가져오기

  • RTCPeerConnections : 미디어 스트림의 지점 간 전달을 위한 로컬 프록시 설정

  • RTCDataChannel : 지점 간 데이터 연결 채널

  • SDP

    • SDP는 세션 설명 프로토콜(SDP로 약칭)이다. SDP는 해상도, 형식, 인코딩, 암호화 알고리즘 등과 같은 멀티미디어 링크 콘텐츠를 설명하는 데 사용되므로 이러한 콘텐츠는 미디어 스트림 자체가 아닙니다. SDP는 프로토콜이 아니라 데이터 형식이다. 해당 데이터 구조는 하나 이상의 UTF-8 텍스트 줄로 구성된다. 각 줄은 한 문자 유형으로 시작하고 그 뒤에 등호("=")가 오고, 형식에 따라 값이나 설명이 포함된 구조화된 텍스트가 온다. 유형. 주어진 문자로 시작하는 텍스트 줄을 종종 "문자 줄"이라고 한다. 예를 들어, 미디어 설명을 제공하는 줄은 “m” 유형이므로 이러한 줄을 "m 줄"이라고 한다.
  • ICE

    • (대화형 연결 설정)은 네트워크에서 안정적인 실시간 통신 연결을 설정하기 위한 프레임워크 및 프로토콜이다. ICE는 별도의 서버가 아니라 STUN(Session Traversal Utilities for NAT) 및 TURN(Traversal Using Relays around NAT) 서버를 활용하여 목표를 달성하는 방식이다. 실제로 ice는 STUN과 TURN의 기능을 통합한 종합 솔루션이다. ICE는 다양한 네트워크 조건에서 최적의 연결 경로를 선택하여 가장 안정적이고 효율적인 통신을 보장한다.
  • STUN 서버

    • NAT(Network Address Translation)로 인해 발생하는 연결 문제를 해결하는 데 사용된다. STUN 프로토콜을 사용하면 클라이언트가 NAT 뒤의 공용 주소와 포트를 발견하여 직접 통신이 가능해진다.
  • TURN 서버

    • 두 엔드포인트가 직접 통신할 수 없는 경우 릴레이 서비스를 제공하는 데 사용된다. TURN 서버는 NAT와 같은 문제를 해결하기 위해 두 끝점 모두에 데이터를 전송하는 중계 역할을 한다.
  • RTP 프로토콜

    • 실시간 대화형 라이브 방송 시스템은 UDP를 사용하여 전송하지만 일반적으로 데이터 스트림을 전송할 때 오디오 및 비디오 데이터를 UDP로 직접 전송하지 않고 오디오 및 비디오 데이터에 RTP 헤더를 먼저 추가하여 전송한다. 그런 다음 전송을 위해 UDP로 전송된다.
  • WebRTC는 기본적으로 실시간 통신을 위해 웹 브라우저를 지원한다.

    • 실시간 통신 데이터에는 플러그인을 다운로드하지 않고도 음성, 오디오, 비디오 및 기타 모든 유형의 데이터가 포함된다.
    • WebRTC를 통해 임의의 데이터를 전송할 수 있다. 이 프로세스는 WebRTC의 데이터 채널에서 수행된다. 서버를 거치지 않고 브라우저 시간에 정보를 전송해야 하는 경우(메시지를 전달하려면 TURN 서버가 필요함)를 사용할 수 있다. 데이터 채널.
    • 데이터 채널은 메시지를 순서대로 또는 비순서적으로 안정적으로 또는 신뢰할 수 없게 전송하도록 구성할 수 있다.
  • WebRTC는 JavaScript API를 사용하는 미디어 엔진이다.

    • WebRTC는 JavaScript API를 상위 계층으로 사용하는 단순한 미디어 엔진이다.
  • WebRTC P2P 데이터 전송 원리

    • P2P는 브라우저 간에 직접 데이터를 보낼 수 있다.
    • 상대방의 네트워크 정보를 얻기 위해서는 먼저 시그널링 서버를 통해 서로의 데이터를 교환해야 한다.
    • 시그널링 서버는 세 가지 유형의 정보를 교환하는 데 사용된다.
      • 세션 제어 메시지: 초기화/종료, 다양한 비즈니스 로직 메시지, 오류 보고
      • 네트워크 메시지: 외부에서 식별 가능한 IP 주소 및 포트(ICE 메시지)
        • 미디어 채널을 연결하기 위해 ICE가 구현된다(때로는 TURN을 통해 메시지를 전달해야 하는 경우도 있음).
        • ICE(Interactive Connectivity Deployment: Interactive Connection 확립)는 STUN, TURN(Traversal Using Relay NAT) 등 다양한 NAT 통과 기술을 통합할 수 있다.
          • STUN 서버는 공용 네트워크에 위치하며 자신의 IP, Port 및 NAT 정보를 얻은 후 이 정보를 시그널링 서버를 통해 교환하고 마지막으로 두 클라이언트가 IP, Port 및 NAT를 기반으로 해당 통신을 수행하도록 한다. 이를 통해 얻은 NAT 정보이다.
        • ICE는 먼저 STUN(NAT 세션 통과 애플리케이션 - 공개 IP 주소 획득)을 사용하여 UDP 기반 연결 설정을 시도한다. 실패할 경우 TCP를 사용하고, 실패할 경우 릴레이 TURN(중간)을 사용한다. ) NAT) 서버를 관통한 후
        • ICE는 클라이언트가 연결을 설정하는 데 가장 적합한 경로를 찾거나 가능한 모든 옵션을 시도하고 가장 적합한 경로를 선택한다.
      • 미디어 기능: 클라이언트는 코덱, 해상도, 통신 대상(SDP 정보)을 제어할 수 있다.
    • 신호가 성공적으로 전송되면 메시지는 두 브라우저 간에 직접 전송될 수 있으며 웹 서버는 이 정보를 얻지 못한다.
  • WebRTC에는 네트워크를 통한 두 가지 유형의 상호 작용이 필요한다.

    • 신호 전송
      • JS 코드를 통해 구현된 HTTPS 연결 또는 WebSocket에서 발생한다.
      • 신호 전달에서 발생해야 하는 모든 작업은 사용자가 서로를 찾고 대화를 시작하는 것이다.
      • 현재 WebSocket+JSON/SDP 솔루션은 업계에서 널리 사용되고 있으며 WebSocket은 신호 전송 채널을 제공하는 데 사용되고 JSON/SDP는 신호의 특정 내용을 캡슐화하는 데 사용된다.
        • WebSocket은 TCP를 기반으로 구축되어 긴 연결 기능을 제공하고 반이중 및 중복 헤더 정보만 지원하는 HTTP의 비효율성 문제를 해결한다.
        • SDP(Session Description Protocol)는 스트리밍 미디어 기능 협상의 신호 내용을 캡슐화하는 데 사용되는 세션 설명 프로토콜이다. 두 개의 WebRTC 에이전트는 이 프로토콜을 통해 연결을 설정하는 데 필요한 모든 상태를 공유한다.
        • 프로토콜은 표준/관례이고, 프로토콜 스택은 프로토콜의 구현으로, 코드, 함수 라이브러리 및 상위 계층 응용 프로그램이 호출할 수 있는 것으로 이해될 수 있다.
    • 전송 매체
      • 미디어 채널(미디어 채널) 사용 필요, SRTP(음성 및 영상용), SCTP(데이터 채널용) 사용
        • SCTP와 SRTP는 멀티플렉싱, 정체 및 흐름 제어 제공, 부분적으로 신뢰할 수 있는 전달 및 부분적으로 UDP 위에 기타 추가 서비스를 제공하는 데 사용된다.
        • DTLS는 반대쪽 끝의 데이터 전송을 보호하는 데 사용된다.
      • 신호와 달리 미디어는 네트워크를 통해 이동하는 데 다른 경로를 사용하고 다르게 동작한다.
  • WebRTC는 미디어와 비디오를 처리한다.

    • WebRTC는 VoIP 기술을 사용하여 미디어를 처리하고 SRTP(안전하고 암호화된 RTP 버전)를 기반으로 네트워크를 통해 전송한다.
    • WebRTC 오디오 및 비디오는 비디오 및 오디오 데이터를 압축하고 압축 해제하는 알려진 알고리즘인 코덱을 사용하여 작동한다.
  • WebRTC 작동 방식에 대한 간략한 설명

    • 오디오, 비디오 또는 모든 데이터를 실시간으로 보낼 수 있다.
    • 브라우저가 서로 액세스할 수 있도록 하려면 NAT 통과 메커니즘을 사용해야 한다.
    • 때로는 P2P가 중계서버(TURN)를 거쳐야 하는 경우도 있다.
    • WebRTC를 사용하려면 신호와 미디어를 서로 분리하여 고려해야 한다.
    • 물론, P2P를 사용하지 않고 미디어 서버를 통해서도 P2P 관련 기능을 구현할 수 있다.
      • 미디어 서버: 여러 사람이 음성 및 영상 통화를 할 때 기존의 P2P 통신은 다소 어려워집니다. 이때 수신하거나 전송해야 하는 클라이언트 수를 줄이고, 중계 역할을 한다.

CentOS에서 Coturn 서버 설치 및 설정하기

  1. Coturn 설치

    Coturn 서버를 설치하기 위해 먼저 CentOS의 패키지 관리자를 통해 설치한다.

    sudo yum install epel-release
    sudo yum install coturn
    
  2. Coturn 설정 파일 수정

    Coturn의 주 설정 파일은 /etc/coturn/turnserver.conf에 위치한다. 이 파일을 수정하여 Coturn 서버를 설정한다.

    sudo vi /etc/coturn/turnserver.conf
    

    주요 설정 항목은 다음과 같다. 각 항목을 필요에 맞게 설정한다.:

    # 리스닝 포트 설정
    listening-port=3478
    
    # 외부 IP 설정 (Coturn 서버의 공인 IP)
    external-ip=<YOUR_EXTERNAL_IP>
    
    # relay IP 설정 (Coturn 서버의 내부 IP)
    relay-ip=<YOUR_INTERNAL_IP>
    
    # 사용자 인증 시크릿 설정
    static-auth-secret=<YOUR_STATIC_AUTH_SECRET>
    
    # realm 설정 (Coturn 서버의 도메인 이름)
    realm=<YOUR_REALM>
    
     lt-cred-mech
    
     user=id:password
    

    위 설정에서 <YOUR_EXTERNAL_IP>, <YOUR_INTERNAL_IP>, <YOUR_STATIC_AUTH_SECRET>, <YOUR_REALM> 부분은 실제로 사용할 서버의 정보로 대체되어야 한다.

  3. Coturn 서버 시작

    Coturn 서버를 시작한다.

    sudo systemctl start coturn
    또는 
    /usr/bin/turnserver -c /etc/turnserver.conf --pidfile /run/coturn/turnserver.pid
    

    서버가 시작되면 Coturn 서버가 지정된 포트에서 실행된다.

  4. 방화벽 설정

    Coturn 서버가 올바르게 작동하려면 방화벽에서 포트를 열어야 한다. 기본적으로 Coturn은 UDP 및 TCP 포트 3478을 사용한다. 필요에 따라 방화벽 설정을 조정한다…

    sudo firewall-cmd --zone=public --add-port=3478/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=3478/udp --permanent
    sudo firewall-cmd --reload
    
  5. 부가적인 설정

    필요에 따라 추가적인 Coturn 설정을 수행할 수 있다. 예를 들어, TLS(SSL)을 사용하거나 TURN REST API를 활성화하는 등의 설정을 변경할 수 있다.

    • TLS 설정: /etc/turnserver.conf에서 TLS 관련 설정을 추가 및 수정한다.
    • TURN REST API 설정: 필요한 경우 REST API를 활성화하고 관련 설정을 수정한다.

Coturn 서버 설정

Coturn 서버와 WebRTC가 작동하는 샘플 코드는 다음과 같은 구성으로 설정할 수 있다. Coturn 서버를 먼저 설정하고, WebRTC 클라이언트를 구성하여 서로 통신하게 한다.

1. Coturn 서버 설정

위의 단계를 따라 Coturn 서버를 설정한다.

2. WebRTC 클라이언트 샘플 코드

다음은 WebRTC를 사용하여 피어 연결을 설정하는 간단한 HTML/JavaScript 샘플 코드이다. 이 코드에서는 Coturn 서버를 사용하여 피어 간의 직접 연결이 불가능한 경우에 중계를 수행한다.

HTML (index.html)

<!DOCTYPE html>
<html>
<head>
    <title>WebRTC Sample</title>
</head>
<body>
    <h1>WebRTC Sample</h1>
    <video id="localVideo" autoplay playsinline></video>
    <video id="remoteVideo" autoplay playsinline></video>

    <script>
        const configuration = {
            iceServers: [
                {
                    urls: 'stun:stun.l.google.com:19302'
                },
                {
                    urls: 'turn:<YOUR_TURN_SERVER_IP>:3478',
                    username: '<YOUR_USERNAME>',
                    credential: '<YOUR_CREDENTIAL>'
                }
            ]
        };

        const localVideo = document.getElementById('localVideo');
        const remoteVideo = document.getElementById('remoteVideo');

        let localStream;
        let peerConnection;

        async function start() {
            localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
            localVideo.srcObject = localStream;

            const servers = null;
            peerConnection = new RTCPeerConnection(configuration);

            peerConnection.addEventListener('icecandidate', event => {
                if (event.candidate) {
                    console.log('New ICE candidate: ', event.candidate);
                }
            });

            peerConnection.addEventListener('track', event => {
                remoteVideo.srcObject = event.streams[0];
            });

            localStream.getTracks().forEach(track => {
                peerConnection.addTrack(track, localStream);
            });

            const offer = await peerConnection.createOffer();
            await peerConnection.setLocalDescription(offer);

            // Normally you'd send the offer to the remote peer through signaling server
            // Here we're simulating it by setting the remote description locally
            await simulateRemoteAnswer(offer);
        }

        async function simulateRemoteAnswer(offer) {
            const remotePeerConnection = new RTCPeerConnection(configuration);
            remotePeerConnection.addEventListener('icecandidate', event => {
                if (event.candidate) {
                    peerConnection.addIceCandidate(event.candidate);
                }
            });

            remotePeerConnection.addEventListener('track', event => {
                remoteVideo.srcObject = event.streams[0];
            });

            localStream.getTracks().forEach(track => {
                remotePeerConnection.addTrack(track, localStream);
            });

            await remotePeerConnection.setRemoteDescription(offer);
            const answer = await remotePeerConnection.createAnswer();
            await remotePeerConnection.setLocalDescription(answer);
            await peerConnection.setRemoteDescription(answer);
        }

        start();
    </script>
</body>
</html>

이 코드는 두 개의 <video> 요소를 사용하여 로컬 및 원격 비디오 스트림을 표시한다. Coturn 서버 정보는 iceServers 배열에 설정되어 있다. 실제로는 시그널링 서버를 통해 offer와 answer를 교환해야 하지만, 여기서는 단순화를 위해 로컬에서 이를 시뮬레이션하고 있다.

  1. <YOUR_TURN_SERVER_IP>을 Coturn 서버의 IP 주소로 바꾸고,
  2. <YOUR_USERNAME><YOUR_CREDENTIAL>을 Coturn 서버에서 설정한 사용자 이름과 비밀번호로 바꿉니다.

이제 웹 서버에서 이 HTML 파일을 호스팅하고 브라우저에서 열면 WebRTC 연결을 테스트할 수 있다.

연결 테스트

서버가 잘 구동(/usr/bin/turnserver -c /etc/turnserver.conf --pidfile /run/coturn/turnserver.pid)되었다면 아래의 사이트에 접속해서
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

STUN or TURN URI: turn:xxx,xxx,xxx,xxx
TURN username:설정화일에서 예제로 입력했던 user=id:password 중 id
TURN password:설정화일에서 예제로 입력했던 user=id:password 중 pssword

Gatther 해해보면

7.836 srflx 4172981128 udp 133.130.186.203 60564 100 | 32030 | 255 stun:xxx.xxx.xxx.xxx:3478
7.910 relay 835235681 udp xxx.xxx.xxx.xxx:3478 59541 2 | 32031 | 255 turn:xxx.xxx.xxx.xxx:3478?transport=udp udp
39.946 Done
처럼 접속이 성공한다.

퀴즈: WebRTC와 Coturn 서버 설정

  1. 질문: WebRTC 애플리케이션에서 Coturn 서버의 주요 목적은 무엇인가?

    답변: Coturn 서버는 WebRTC 애플리케이션에서 클라이언트 간의 통신을 중계하는 역할을 한다. 직접 연결이 불가능할 때 NAT 트래버셜(Traversal)을 가능하게 하고, 시그널링과 ICE(Interactive Connectivity Establishment) 프레임워크를 지원하여 연결을 용이하게 한다.

  2. 질문: Cent에서 Coturn을 설치하기 위해 사용하는 명령어는 무엇인가?

    답변: 아래 명령어를 사용한다:

     CentOS
     sudo yum install epel-release 
     sudo yum install coturn
     
     우분투라면
     sudo apt-get -y update
     sudo apt-get -y install coturn
    
  3. 질문: Coturn 설정 파일(/etc/turnserver.conf)에 반드시 포함해야 할 주요 매개변수는 무엇인가?

    답변: Coturn 설정 파일에는 다음과 같은 주요 매개변수가 포함되어야 한다:

    listening-ip:0.0.0.0
    listening-port=3478
    relay-ip=<YOUR_SERVER_IP>
    external-ip=<YOUR_EXTERNAL_IP>
    static-auth-secret=<YOUR_STATIC_AUTH_SECRET>
    realm=<YOUR_REALM>(도에인.컴)
    min-port=10000
    max-port=20000
    user=id:password
    
  4. 질문: 사용자 지정 설정으로 Coturn 서버를 시작하는 방법은 무엇인가?

    답변: 다음 명령어로 Coturn 서버를 시작할 수 있다:

    sudo turnserver -c /etc/turnserver.conf -a -v
    
  5. 질문: Coturn이 올바르게 작동하도록 하기 위해 열어야 할 방화벽 포트는 무엇인가?

    답변: 방화벽에서 포트 3478과 5349 (TLS)를 열어야 한다:

    sudo ufw allow 3478/tcp
    sudo ufw allow 3478/udp
    sudo ufw allow 5349/tcp
    sudo ufw allow 5349/udp
    또는 
    sudo firewall-cmd --zone=public --add-port=3478/udp --permanent 
    sudo firewall-cmd --zone=public --add-port=5349/tcp --permanent 
    sudo firewall-cmd --reload
    또는
    sudo iptables -A INPUT -p udp --dport 3478 -j ACCEPT 
    sudo iptables -A INPUT -p tcp --dport 5349 -j ACCEPT 
    sudo service iptables save
    
  6. 질문: WebRTC 클라이언트 설정에서 iceServers는 무엇을 위해 사용되나?

    답변: iceServers는 클라이언트가 ICE 프레임워크를 통해 NAT 트래버셜을 수행할 수 있도록 STUN 및 TURN 서버 정보를 제공하는 역할을 한다.

  7. 질문: 아래 iceServers 설정을 수정하여 자신의 Coturn 서버를 사용하도록 변경하는 방법은 무엇인가?

    const configuration = {
        iceServers: [
            {
                urls: 'stun:stun.l.google.com:19302'
            },
    

    답변: urls 항목을 자신의 Coturn 서버의 주소로 변경하고, usernamecredential을 설정하여 아래와 같이 수정한다:

    const configuration = {
        iceServers: [
            {
                urls: 'turn:<YOUR_TURN_SERVER_IP>:3478',
                username: '<YOUR_USERNAME>',
                credential: '<YOUR_CREDENTIAL>'
            },
    

추가 퀴즈: NAT 트래버셜, 시그널링, ICE 프레임워크

  1. 질문: NAT 트래버셜(Traversal)이란 무엇을 의미하나?

    답변: NAT 트래버셜은 네트워크 주소 변환(NAT)과 방화벽을 통과하여 피어 간의 직접적인 연결을 가능하게 하는 기술이다. NAT 트래버셜은 피어 간의 네트워크 주소를 교환하고 중계 서버(STUN 및 TURN)를 통해 데이터를 전달하여 연결을 용이하게 한다.

  2. 질문: WebRTC에서 시그널링 서버의 역할은 무엇인가?

    답변: 시그널링 서버는 WebRTC 피어 간의 연결을 설정하기 위해 제안(Offer) 및 응답(Answer) SDP를 중계하고 피어 간의 네트워크 설정 메타데이터를 전송하는 역할을 한다. 시그널링 서버는 피어가 서로의 위치를 알 수 있도록 도와주며, 연결 세션을 초기화하고 해제하는 역할을 담당한다.

  3. 질문: ICE(Interactive Connectivity Establishment) 프레임워크는 어떤 역할을 하나?

    답변: ICE 프레임워크는 네트워크 주소 변환(NAT)과 방화벽을 통과하여 최적의 연결 경로를 찾아주는 기술이다. ICE는 STUN(회전 서버)과 TURN(트래버스 서버)을 사용하여 클라이언트가 네트워크 환경에서 직접 피어 간 연결을 수립할 수 있도록 돕다.

  4. 질문: ICE 프레임워크에서 ICE 후보(Candidate)란 무엇을 나타내나?

    답변: ICE 후보는 클라이언트가 연결할 수 있는 네트워크 인터페이스의 IP 주소, 포트 및 프로토콜을 나타냅니다. ICE 후보는 클라이언트가 네트워크 환경을 탐색하고 최적의 연결 경로를 선택하는 데 사용된다.

  5. 질문: ICE 프레임워크에서 STUN 서버와 TURN 서버의 차이점은 무엇인가?

    답변: STUN(세션 트래버셜 유틸리티 네트워크) 서버는 클라이언트의 공인 IP 주소와 포트를 발견하는 데 사용된다. 반면, TURN(회전 서버)은 클라이언트가 직접 연결할 수 없는 경우 데이터 중계를 제공하여 피어 간의 통신을 가능하게 한다.

  6. 질문: ICE 프레임워크에서 Candidate Gathering는 어떻게 이루어지나요?

    답변: Candidate Gathering은 클라이언트가 가능한 모든 네트워크 인터페이스를 탐색하고, 각 인터페이스의 IP 주소와 포트를 수집하는 과정이다. 이 후보들은 ICE 프레임워크를 통해 다른 피어와의 연결을 시도할 때 사용된다.

  7. 질문: WebRTC에서 ICE 커넥션 상태가 new, checking, connected, completed, failed, disconnected일 때 각각 무슨 의미인가?

    답변:

    • new: ICE 커넥션 객체가 생성되었지만 아직 초기화되지 않음.
    • checking: ICE 후보를 수집하고 연결 가능성을 검사 중.
    • connected: ICE 커넥션을 통해 피어 간의 데이터 통신이 성공적으로 이루어짐.
    • completed: ICE 프로세스가 완료되고 최적의 연결 경로가 선정됨.
    • failed: ICE 후보를 수집하고 연결을 시도했으나 실패한 상태.
    • disconnected: ICE 커넥션에서 피어와의 연결이 끊어짐.
  8. 질문: ICE 프레임워크에서 ICE 커넥션 객체의 역할은 무엇인가?

    답변: ICE 커넥션 객체는 클라이언트 간의 통신을 관리하는 중요한 역할을 한다. ICE 후보의 수집, 검사, 연결 설정 및 관리를 통해 피어 간의 최적의 네트워크 연결을 제공한다.

  9. 질문: ICE 프레임워크에서 ICE 실패(Failure)는 어떤 경우에 발생하나?

    답변: ICE 실패는 클라이언트가 모든 가능한 후보를 수집하고 검사한 후에도 연결을 설정할 수 없는 경우 발생한다. 이는 네트워크 환경이나 방화벽 설정 등의 문제로 인해 발생할 수 있다.

  10. 질문: WebRTC에서 SDP(Session Description Protocol)가 ICE와 어떻게 관련되나?

    답변: SDP는 WebRTC 세션을 설정하기 위해 피어 간에 교환되는 메타데이터 형식이다. SDP는 ICE 프레임워크를 통해 생성된 후보들과 네트워크 연결 설정을 포함하며, 피어 간의 미디어 스트림을 정의하는 데 사용된다.

추가 퀴즈: WebRTC 소스 코드

  1. 질문: WebRTC에서 getUserMedia() 메서드는 무엇을 하나?

    답변: getUserMedia() 메서드는 웹 브라우저에서 사용자의 오디오와 비디오를 얻기 위해 사용된다. 이를 통해 웹 애플리케이션은 사용자의 카메라와 마이크에 접근하여 미디어 스트림을 획득할 수 있다.

  2. 질문: RTCPeerConnection 객체는 무엇을 나타내나?

    답변: RTCPeerConnection 객체는 WebRTC 피어 간의 연결을 나타내며, 오디오와 비디오 스트림을 교환할 수 있도록 도와줍니다. ICE 프레임워크를 사용하여 NAT 트래버셜과 통신을 관리하며, 미디어 스트림의 효과적인 전송을 가능하게 한다.

  3. 질문: ICE 프레임워크에서 ICE의 역할은 무엇인가?

    답변: ICE(Interactive Connectivity Establishment)는 WebRTC에서 피어 간의 네트워크 연결을 설정하는 프레임워크이다. 네트워크 주소 변환(NAT)과 방화벽을 통과하면서 최적의 연결 경로를 찾아주며, STUN 및 TURN 서버를 사용하여 연결을 가능하게 한다.

  4. 질문: createOffer()createAnswer() 메서드는 각각 무엇을 하나?

    답변: createOffer() 메서드는 로컬 피어에게 연결을 제안하는 SDP(Session Description Protocol) 오퍼를 생성한다. createAnswer() 메서드는 오퍼에 대한 응답으로 SDP 앤서를 생성하여 원격 피어와의 연결을 수립한다.

  5. 질문: addTrack() 메서드는 어떤 역할을 하나?

    답변: addTrack() 메서드는 로컬 스트림에서 트랙을 추가하여 RTCPeerConnection에 연결한다. 이를 통해 오디오 및 비디오 트랙을 원격 피어에게 전송할 수 있다.

  6. 질문: ICE 후보(Candidate)는 무엇을 나타내나?

    답변: ICE 후보는 클라이언트가 다른 피어와 연결할 수 있는 네트워크 인터페이스의 IP 주소, 포트 및 프로토콜을 나타냅니다. ICE 후보는 RTCPeerConnection을 통해 생성되고 교환되어 연결을 설정하는 데 사용된다.

  7. 질문: icecandidate 이벤트는 어떤 때 발생하나?

    답변: icecandidate 이벤트는 ICE 프레임워크가 ICE 후보를 생성할 때 발생한다. 이 이벤트는 ICE 후보가 생성되었음을 나타내며, 원격 피어와의 연결을 설정하는 데 필요한 정보를 포함한다.

  8. 질문: track 이벤트는 어떤 때 발생하나?

    답변: track 이벤트는 원격 피어에서 새로운 미디어 트랙이 수신될 때 발생한다. 이 이벤트를 통해 원격 피어로부터 전송되는 오디오 및 비디오 스트림을 수신할 수 있다.

  9. 질문: WebRTC의 SDP(Session Description Protocol)는 무엇을 포함하나?

    답변: SDP는 웹 브라우저 간에 미디어 세션을 설정하는 데 사용되는 텍스트 기반 프로토콜이다. SDP에는 미디어 유형, 포트, 코덱 및 네트워크 정보가 포함되어 있다.

  10. 질문: WebRTC에서 시그널링 서버의 역할은 무엇인가?

    답변: 시그널링 서버는 웹RTC 피어 간의 연결을 설정하기 위해 제안(Offer) 및 응답(Answer) SDP를 교환하는 중개 역할을 한다. 시그널링 서버는 피어가 서로 통신할 수 있도록 필요한 메타데이터를 전송한다.

WebRTC 관련 오픈소스

kurento-media-server

출처 : https://qiita.com/Otama75/items/7153607d3cde868aaf5c
추가 : https://onetech.jp/blog/makes-webrtc-applications-easier-with-kurento-5435
docker : https://hub.docker.com/r/kurento/kurento-media-server-dev
참고프로젝트 :https://velog.io/@jupiter-j/프로젝트-종료-EyesTalk

Kurento는 오픈 소스 WebRTC 미디어 서버입니다. 화상 통화 및 화상 회의와 같은 미디어 스트리밍 기능을 갖춘 웹 애플리케이션을 구축하는 메커니즘을 제공합니다. Kurento 이외에도 이러한 WebRTC 미디어 서버에는 여러 가지가 있지만 Kurento의 특징 중 하나는 MCU에 의한 화상 회의 앱을 구축할 수 있다는 것입니다.

openvidu

자료 : https://velog.io/@duddnd904/OpenVidu-총정리

간단설치방법 :https://gigazine.net/news/20200504-openvidu/

안드로이드 오픈미팅: https://gigazine.net/news/20201129-apache-openmeetings/