개발자 준의 기술블로그


  • 홈

  • 아카이브

  • 태그

  • 검색

[WebRTC] Intel Open WebRTC Toolkit 클라이언트 삽질기

작성일 2020-06-11 | In Frontend

Client코드를 작성하기 앞서, Intel에서 제공한 샘플코드를 참고하여 사용하는 법을 익히려고 합니다.

모든 코드는 아래의 참고자료의 owt-client-javascript repo를 기준으로 합니다

src/samples/conference/public/scripts/index.js를 보면 기본적인 conference를 생성/접속하는 법이 나와있는 것 같다.
134번째 줄부터 시작하는 window.onload에 createToken을 하고, 만들어진 tokenString을 callback함수에 넘겨 conference에 입장한다.

createToken()은 src/samples/conference/public/scripts/rest-sample.js에 있다. 이 부분은 자신의 필요에 맞게 바꾸면 될것 같다.

그리고 조금 더 내려가면 가장 중요한 conference.join(token).then( ....이 있는데 여기서 token이 어떤정보를 가지고 있어야되는지를 알아야한다. src/sdk/conference/client.js를 보면 ConferenceClient 클래스 안에 this.join이 있다.

Custom signaling

custom signaling을 사용하려면 conference = new Owt.Conference.ConferenceClient();로 ConferenceClient를 생성할 때 두번째 인자로 넣어주면된다. 이 객체는 .send(), .connect(), .disconnect()를 가지고 있어야하며 disconnect했을때와 참여자, 스트림 등이 바뀌었을때 처리할 수 있도록 disconnect와 data 이벤트를 발생시켜야한다. 실제 스트림 등 데이터를 전달하고 싶으면 data 이벤트를 발생시켜 처리힌다. 그러면 ConferenceClient는 onSignalingMessage()를 호출해 처리한다.

new Owt.Conference.ConferenceClient(config, signalImp)

두번째인자는 앞에서 언급한대로 자신만의 signaling을 구현하고 싶을때 넘겨주고, config는 sdk > conference > client.js의 createPeerConnectionChannel()에서 new ConferencePeerConnectionChannel(config, signalingForChannel)에서 인자로 넘어간다. 그러므로 config의 host에는 media server로 연결되는 host가 있어야되는 것 같다.

conference.join(tokenString)

join을 살펴보면 처음에 tokenString을 Base64.decodeBase64로 복호화를 한다. 따라서 toikenString은 base64로 암호화되어있어야한다.
그리고 아래 나오는 코드를 보면 join에서 token.secure와 token.host를 접근하는데 token.secure은 정의되지않아도 (token.secure === true)에 의해 false로 바뀌어 isSecure에 저장되므로 괜찮다. token.host는 http 프로토콜을 붙이지 않으면 isSecure를 검사해서 http나 https를 붙여주는데 만약 자신의 서버가 https://인데 token.secure를 true로 하지도 않고 host에도 붙이지 않으면 잘못된 host로 접근할 것이다.

signalingState는 READY, CONNECTING, CONNECTED가 있으며 접속하는 중이거나 이미 접속된 상태에서 join을 호출하면 더 이상 진행하지않고 종료한다. 아무와도 연결되지않은 READY상태라면 CONNECTING으로 바꾸고 계속 진행한다.

아직 이해되지 않는점:
conference.join(tokenString)을 호출하면 tokenString을 decode하고, token에서 host를 signaling.connect(host)로 넘겨준다. 그렇다면 coference.join(token)의 host는 signaling 서버의 url이라는 뜻인데.. 이전에 연결할때는 wss프로토콜을 사용하였었다. 그런데 conference.join에서는 wss가 아니라 https 혹은 http를 붙여서 보내주는데 음.. 뭔가 잘못이해한건가

참고자료:
https://software.intel.com/sites/products/documentation/webrtc/js/index.html

https://github.com/open-webrtc-toolkit

https://github.com/open-webrtc-toolkit/owt-client-javascript

더 읽어보기 »

[Vanilla JS] VanillaJS로 구현한 AJAX

작성일 2020-06-11 | In Frontend
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
export const parseJSXstr = (parent, JSXstr) => {
    const processedJSXstr = preprocessJSXstr(JSXstr)
    const result = generateObject(processedJSXstr, 0)
    const generatedJSXobj = result.JSXobj
    parseJSXobj(parent, generatedJSXobj)
}

const parseJSXobj = (parent, JSXobj) => {
    const ignorePropertyList = ["type", "children"]
    
    const $element = document.createElement(JSXobj.type)
    // Handle properties that need to be added to the element
    for (let prop in JSXobj){
        if(!ignorePropertyList.includes(prop))
            $element[matchPropName(prop)] = JSXobj[prop]
    }
    // Handle children elements
    if("children" in JSXobj){
        JSXobj.children.forEach((childElem, index) => {
            parseJSXobj($element, childElem)
        })
    }
    parent.appendChild($element)
}

const stripString = (str) => {
    return str.replace(/^\s+|\s+$/g, '');
};

const matchPropName = (prop) => {
    const substituteReacttoVanilla = {
        text: "textContent",
        onClick: "onclick"
    }
    if(prop in substituteReacttoVanilla)
        return substituteReacttoVanilla[prop]
    else
        return prop
}

const preprocessJSXstr = (JSXstr) => {
    const splitStr = JSXstr.split('<')
    return splitStr
    .filter(str => {
        //Handle empty string elements
        if(!stripString(str))
            return false
        return true
    })
    .map(str => {
        return stripString(str)
    })
}

const generateObject = (arr, checkIndex) => {
    let obj = { children: [] }
    let generatedObj
    //Found Closing Tag
    if(arr[checkIndex][0] == '/'){
        return {
            nextCheckIndex: checkIndex + 1,
            JSXobj: null
        }
    }
    //If not starting with closing tag
    else {
        generatedObj = generateObject(arr, checkIndex+1)
        //loop until my closing tag appears
        while(true){
            //return from children element
            if(generatedObj.JSXobj != null){
                obj.children.push(generatedObj.JSXobj)
                generatedObj = generateObject(arr, generatedObj.nextCheckIndex)
            }
            //return from closing tag
            else
                break
        }
    }

    let element = arr[checkIndex]

    //Cut out onClick function string
    const searchTermStart = " onClick"
    const startIndex = element.indexOf(searchTermStart)
    if(startIndex !== -1){
        const searchTermEnd = "}"
        const endIndex = element.lastIndexOf(searchTermEnd)
        
        //Assign onclick
        const tempFuncStr = element.slice(startIndex,endIndex)
        const funcStartIndex = tempFuncStr.indexOf("{")
        obj.onclick = eval(tempFuncStr.slice(funcStartIndex+1, endIndex))

        const tempElement = element.slice(0, startIndex) + element.slice(endIndex+1,element.length)
        element = tempElement
    }

    const elementContent = element.split('>')
    obj.text = elementContent[1]

    const elementProps = elementContent[0].split(' ')
    elementProps.forEach((prop, index) => {
        if(index === 0)
            obj.type = prop
        else{
            //split prop name and value
            const splitProp = prop.split('=')
            const propName = splitProp[0]
            const propValue = splitProp[1].replace(/"/g,"").replace(/'/g,"")
            obj[propName] = propValue
        }
    })
    return {
        nextCheckIndex: generatedObj.nextCheckIndex,
        JSXobj: obj
    }
}

더 읽어보기 »

[Vanilla JS] VanillaJS로 구현한 AJAX

작성일 2020-06-11 | In Frontend
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
export const parseJSXstr = (parent, JSXstr) => {
    const processedJSXstr = preprocessJSXstr(JSXstr)
    const result = generateObject(processedJSXstr, 0)
    const generatedJSXobj = result.JSXobj
    parseJSXobj(parent, generatedJSXobj)
}

const parseJSXobj = (parent, JSXobj) => {
    const ignorePropertyList = ["type", "children"]
    
    const $element = document.createElement(JSXobj.type)
    // Handle properties that need to be added to the element
    for (let prop in JSXobj){
        if(!ignorePropertyList.includes(prop))
            $element[matchPropName(prop)] = JSXobj[prop]
    }
    // Handle children elements
    if("children" in JSXobj){
        JSXobj.children.forEach((childElem, index) => {
            parseJSXobj($element, childElem)
        })
    }
    parent.appendChild($element)
}

const stripString = (str) => {
    return str.replace(/^\s+|\s+$/g, '');
};

const matchPropName = (prop) => {
    const substituteReacttoVanilla = {
        text: "textContent",
        onClick: "onclick"
    }
    if(prop in substituteReacttoVanilla)
        return substituteReacttoVanilla[prop]
    else
        return prop
}

const preprocessJSXstr = (JSXstr) => {
    const splitStr = JSXstr.split('<')
    return splitStr
    .filter(str => {
        //Handle empty string elements
        if(!stripString(str))
            return false
        return true
    })
    .map(str => {
        return stripString(str)
    })
}

const generateObject = (arr, checkIndex) => {
    let obj = { children: [] }
    let generatedObj
    //Found Closing Tag
    if(arr[checkIndex][0] == '/'){
        return {
            nextCheckIndex: checkIndex + 1,
            JSXobj: null
        }
    }
    //If not starting with closing tag
    else {
        generatedObj = generateObject(arr, checkIndex+1)
        //loop until my closing tag appears
        while(true){
            //return from children element
            if(generatedObj.JSXobj != null){
                obj.children.push(generatedObj.JSXobj)
                generatedObj = generateObject(arr, generatedObj.nextCheckIndex)
            }
            //return from closing tag
            else
                break
        }
    }

    let element = arr[checkIndex]

    //Cut out onClick function string
    const searchTermStart = " onClick"
    const startIndex = element.indexOf(searchTermStart)
    if(startIndex !== -1){
        const searchTermEnd = "}"
        const endIndex = element.lastIndexOf(searchTermEnd)
        
        //Assign onclick
        const tempFuncStr = element.slice(startIndex,endIndex)
        const funcStartIndex = tempFuncStr.indexOf("{")
        obj.onclick = eval(tempFuncStr.slice(funcStartIndex+1, endIndex))

        const tempElement = element.slice(0, startIndex) + element.slice(endIndex+1,element.length)
        element = tempElement
    }

    const elementContent = element.split('>')
    obj.text = elementContent[1]

    const elementProps = elementContent[0].split(' ')
    elementProps.forEach((prop, index) => {
        if(index === 0)
            obj.type = prop
        else{
            //split prop name and value
            const splitProp = prop.split('=')
            const propName = splitProp[0]
            const propValue = splitProp[1].replace(/"/g,"").replace(/'/g,"")
            obj[propName] = propValue
        }
    })
    return {
        nextCheckIndex: generatedObj.nextCheckIndex,
        JSXobj: obj
    }
}
더 읽어보기 »

[Web Server 이해하기] Apache vs Nginx

작성일 2020-03-18 | In Webserver

사이트에 ssl을 적용하지 않으면 안되는 상황이 많아지면서 nginx에서 요청을 받아 ssl을 풀고 실제로 요청을 처리하는 docker container로 전달하는 경우가 많아졌습니다. 작업을 하다가 이와 같은 경우에 어떻게 해야되는지를 정리해보았습니다

아래는 docker container들을 관리해주는 docker-compose.yml입니다.ㄷ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway

실제로 요청을 처리하는 컨테이너는 janus-gateway이고, nginx는 단순히 ssl을 해제하여 맞는 포트로 포워딩만 시켜줍니다.

nginx가 외부에서 접근하는 8088포트를 바인딩하여 자신의 컨테이너의 8088포트로 보내줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:8088;
    }
  }
}

그 후 nginx에서 nginx.conf에서 nginx가 docker-compose.yml에서 설정하였듯 외부포트의 8088로 접근하는 요청을 내부 8088포트로 돌려줬기때문에 `listen 8088`을 사용하여 내부포트 8088에 바인딩해줍니다.

ssl을 해제한 이후 http://:<실제 포트="">로 전달하여줍니다.

여기까지는 간단한데, 헷갈렸던 점이 위의 docker-compose.yml같이 janus-gateway 컨테이너에 아무 port가 명시되어있지 않으면 외부로부터 요청을 받지 못하는 줄 알았는데, 내부포트는 전부 열려있고, 단지 자체적으로 외부 포트로의 바인딩이 되어있지 않은점입니다. 따라서 외부포트로부터의 요청을 하나도 받지 못하지만 위처럼 nginx가 내부의 8088번 포트로 포워딩해주는 요청들은 전부 받을 수 있습니다.

또, 처음에는 이 사실을 몰랐기에:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    ports:
      - "9088:8088"
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:9088;
    }
  }
}

위와 같이 docker-compose.yml에서 janus-gateway를 9088번 포트에서 요청을 받아서 실제로 요청을 처리하는 8088번 포트로 포워딩해주고(왜냐하면 8088번으로부터 외부에서 들어오는 요청에는 ssl이 적용되어있어 nginx에서 ssl을 해제하여 포워딩해주어야합니다), 외부에서 8088번포트로 들어오는 요청은 nginx에서 받아서 janus-gateway 컨테이너의 9088번 포트로 포워딩해주었습니다.

해보니 당연히 작동이 되지않았습니다. janus-gateway에서 바인딩한 9088번포트는 해당 컨테이너가 돌아가고있는 물리적인 머신의 9088번 포트로 들어오는 요청을 컨테이너의 8088번포트로 연결해주는 것이었기때문에, nginx에서 컨테이너의 9088번포트로 보내주어도, 요청을 받지 못합니다.

더 읽어보기 »

[Web Server 이해하기] Docker와 Nginx 포트 포워딩

작성일 2020-03-18 | In Webserver

사이트에 ssl을 적용하지 않으면 안되는 상황이 많아지면서 nginx에서 요청을 받아 ssl을 풀고 실제로 요청을 처리하는 docker container로 전달하는 경우가 많아졌습니다. 작업을 하다가 이와 같은 경우에 어떻게 해야되는지를 정리해보았습니다

아래는 docker container들을 관리해주는 docker-compose.yml입니다.ㄷ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway

실제로 요청을 처리하는 컨테이너는 janus-gateway이고, nginx는 단순히 ssl을 해제하여 맞는 포트로 포워딩만 시켜줍니다.

nginx가 외부에서 접근하는 8088포트를 바인딩하여 자신의 컨테이너의 8088포트로 보내줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:8088;
    }
  }
}

그 후 nginx에서 nginx.conf에서 nginx가 docker-compose.yml에서 설정하였듯 외부포트의 8088로 접근하는 요청을 내부 8088포트로 돌려줬기때문에 `listen 8088`을 사용하여 내부포트 8088에 바인딩해줍니다.

ssl을 해제한 이후 http://:<실제 포트="">로 전달하여줍니다.

여기까지는 간단한데, 헷갈렸던 점이 위의 docker-compose.yml같이 janus-gateway 컨테이너에 아무 port가 명시되어있지 않으면 외부로부터 요청을 받지 못하는 줄 알았는데, 내부포트는 전부 열려있고, 단지 자체적으로 외부 포트로의 바인딩이 되어있지 않은점입니다. 따라서 외부포트로부터의 요청을 하나도 받지 못하지만 위처럼 nginx가 내부의 8088번 포트로 포워딩해주는 요청들은 전부 받을 수 있습니다.

또, 처음에는 이 사실을 몰랐기에:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    ports:
      - "9088:8088"
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:9088;
    }
  }
}

위와 같이 docker-compose.yml에서 janus-gateway를 9088번 포트에서 요청을 받아서 실제로 요청을 처리하는 8088번 포트로 포워딩해주고(왜냐하면 8088번으로부터 외부에서 들어오는 요청에는 ssl이 적용되어있어 nginx에서 ssl을 해제하여 포워딩해주어야합니다), 외부에서 8088번포트로 들어오는 요청은 nginx에서 받아서 janus-gateway 컨테이너의 9088번 포트로 포워딩해주었습니다.

해보니 당연히 작동이 되지않았습니다. janus-gateway에서 바인딩한 9088번포트는 해당 컨테이너가 돌아가고있는 물리적인 머신의 9088번 포트로 들어오는 요청을 컨테이너의 8088번포트로 연결해주는 것이었기때문에, nginx에서 컨테이너의 9088번포트로 보내주어도, 요청을 받지 못합니다.

더 읽어보기 »

[Web Server 이해하기] CGI / WAS / WSGI

작성일 2020-03-18 | In Webserver

웹서버의 기본적인 역할은 정적(static)인 파일을 그대로 보내주는 것입니다. 그 말인즉슨, 뭔가 별도의 처리를 거치지 않고 존재하는 파일을 그대로 보내주는 역할을 합니다.

CGI(Common Gateway Interface)

웹서버에서 어플리케이션을 작동시키기 위한 인터페이스로서, 정적인 웹서버를 동적으로 기능하게 하기 위해서 등장하였습니다. 기존에는 외부프로그램이 필요한 리퀘스트가 들어오면 CGI를 통해 따로 프로세스를 fork하고 외부프로그램을 실행시켜 처리하였지만, 요즘에는 웹서버에 인터프리터를 내장시켜 내부적으로 처리한다. CGI Programming이란, Perl, C/C++를 사용하여 웹서버가 실행할 수 있는 프로그램을 작성하는 것이다.

WAS(Web Application Server)

웹서버가 동적으로 기능하면 WAS이다. 즉, Web Server + CGI가 WAS이다.

WSGI Server(middleware)

Web server와 WSGI를 지원하는 Web Application사이에서 동작하며 아래와 같은 일을 한다

1
2
3
- 환경변수가 바뀌면 타겟 URL에 따라서 리퀘스트 경로를 지정해줌

- 같은 프로세스에서 여러 어플리케이션과 프레임워크가 실행됨

WSGI vs CGI

CGI는 리퀘스트가 들어오면 CGI 프로토콜에 따라서 스크립트를 실행시킵니다. 서브프로세스를 fork하여 서브프로세스가 response를 작성하고 이를 웹서버로 보내면 웹서버가 이 response를 브라우저로 보냅니다. 대부분 CGI는 모든 리퀘스트마다 서브프로세스를 fork합니다.

WSGI는CGI 디자인 패턴에 기반한 인터페이스입니다. 가장 큰 차이점은 WSGI는 모든 리퀘스트마다 fork를 통해 서브프로세스를 띄우지 않으므로 느리지 않습니다.

https://brownbears.tistory.com/350”>https://brownbears.tistory.com/350

왜 WSGI가 중요한가?

전통적인 Web Server는 파이썬 어플리케이션들을 이해하거나 실행시킬 수가 없다. 그래서 처음 Apache와 함께 사용하기 위해 mod_python이라는 모듈이 개발되었지만, 시간이 지남에 따라 보안 이슈와 개발 지연등의 이유로 새로운 방법이 필요하게 되었다. 따라서 파이썬 커뮤니티는 WSGI라는 모듈과 컨테이너들이 도입할 수 있는 표준 인터페이스를 작성하게 되었다.

WSGI의 목적

왜 Web server가 직접 application으로 포워딩하지 않고 WSGI를 거쳐야될까?

  • 유연성

개발자는 웹서버를 바꿔도 웹 어플리케이션의 코드를 수정할 필요가 없다.

또, WSGI를 중간에 바꾸더라도 상관이 없다(Green Unicorn -> uWSGI)

즉, 사용자가 상황에 맞는 스택으로 쉽게 쉽게 바꿀 수 있다.

  • 확장성

수천 건의 request를 처리하는 것은 framework의 역할이 아니라 WSGI server의 역할이다. WSGI 서버들은 request들을 어떻게 framework에 보낼지를 결정한다. 역할 분담은 확장성에 매우 중요하다.

https://www.fullstackpython.com/wsgi-servers.html”>https://www.fullstackpython.com/wsgi-servers.html

더 읽어보기 »

[Web Server 이해하기] Nginx의 역할 (Feat. gunicorn)

작성일 2020-03-18 | In Webserver

Ningx는 request가 가장 먼저 도착하는 곳이다. Web application으로 보내져야만 하는 request들만을 보낸다.(필터링같은 느낌)

Gunicorn은 request를 웹 프레임워크가 이해하고 처리할 수 있는 형식으로 변환시켜 보낸다.

Nginx

Nginx는 Web server이자 Reverse Proxy이다. 아래는 Nginx가 잘하는 기능들 예시이다: - domain name routing - 정적 파일 보내기 - 한번에 들어오는 많은 양의 request를 처리하기 - 느린 client들 처리하기 - 동적처리가 필요한 request들을 Gunicorn으로 보내기 - SSL 끝내기

Nginx는 다음과 같은 일들은 할 수 없다: - Python web application 실행하기 - request를 WSGI로 변역하기

Gunicorn

Gunicorn은 다음과 같은 일들에 특화되어 있다: - worker processes/threads 풀을 사용하여 코드 실행 - Nginx가 보낸 request를 WSGI에 맞게 변환 - 어플리케이션에서 보낸 WSGI response를 http response로 변환 - request가 들어오면 실제로 파이썬 코드 실행하기 - Gunicorn은 다양한 웹서버와 통신할 수 있음

다음과 같은 일은 할 수 없다: - 최전방에 나설 수 없다: DOS에 취약하다 - SSL을 끝낼 수 없다(no https handling)

더 읽어보기 »

[Web Server 이해하기] Nginx configuration

작성일 2020-03-18 | In Webserver

1. Upstream Block
Upstream은 서버들의 묶음이다.
upstream backend {
    server 111.111.111:8000 weight=5;
    server 111.111.112:8000;
    server 111.111.113:8000;
}
위의 예시에서는 3개의 서버간에 load balancing이 되며 weight를 명시하게 되면 해당 서버에 더 많은 load를 배분할 수 있다. 위의 예시에서는 111.111.111서버가 다른 2서버에 비해 5배 많은 load를 할당받는다.

configuration 파일에서 server블럭에서는 ip주소로 호출하는 것이 아니라 upstream의 이름으로 호출한다. (예시에서 backend)

2. Server Block
Server Block은 하나의 가상 서버를 나타낸다. 따라서 여러개의 server block을 생성하게 되면 하나의 머신에서 여러개의 어플리케이션을 호스팅할 수 있다.

Nginx가 라우팅을 할때는 최우선 적으로 listen 옵션을 본다. listen 옵션은
    - address:port 숫자
    - ip주소 (포트80만 처리)
    - port 숫자 (해당 포트로 들어오는 모든 인터페이스)
    - unix socket 경로

Nginx는 오직 같은수준으로 명시된 listen이 여러개 있을 때만 server_name을 사용하여 비교한다.

1
2
3
4
5
6
7
8
9
server {
    listen 192.168.1.10;
    . . .
}
server {
    listen 80;
    server_name example.com;
    . . .
}

위의 예시에서 만약 example.com이 192.168.1.10의 80번 포트에 호스팅 되어 있다면, 항상 첫번째 블럭이 호출된다(Nginx는 불완전한 ip주소를 자동으로 채우므로 192.168.1.10:80으로 변환된다)

더 읽어보기 »

[Machine Learning] Attention Mechanism

작성일 2019-12-06 | In MachineLearning

(개인적으로 학습의 목적으로 정리한 글로 틀린 내용이 있을 수 있습니다)

Attention이 생긴이유 1) 연속된 정보(Sequence data)들관의 상관관계를 처리하기 위해 RNN(Recurrent Neural Network)이 처음으로 고안되었다.

2) 하지만 RNN은 vanishing gradient의 문제점 때문에 데이터가 길어지면 길어질수록 결과가 부정확해졌고, LSTM(Long Short Term Memory)는 이를 보완하기 위해 구조를 개선하여 나왔다.

3) Seq2Seq 모델은 내부적으로 encoder와 decoder라는 2개의 LSTM/RNN 모델 계층들을 사용하여 sequence를 입력값으로 받고 결과값도 sequence로 반환하여준다.

4) 하지만 Seq2Seq 모델또한 Vanishing Gradient문제가 남아있고, 하나의 고정된 벡터에 모든 정보를 압축하려고 하니까 정보 손실이 발생한다. 이를 보완하기 위해 Attention Mechanism이 사용되고 있다.

Seq2Seq 모델을 간단하게 짚고 넘어가기! Seq2Seq 모델은 encoder과 decoder로 이루어지는데, encoder와 decoder는 하나 혹은 여러개의 RNN/LSTM의 계층을 가집니다. Encoder는 입력값을 압축해서 context vector로 변환합니다. 그리고 decoder는 이 context vector를 기반으로 각각의 timestamp마다 결과값을 추론하며, 얻어진 결과값을 다음 timestamp의 입력값으로 사용합니다. decoder는 이 작업을 부터 시작하여 까지 반복합니다. 추론의 결과값이 가 나오면 작업을 끝냅니다.

Attention의 기본적인 아이디어 Attention의 기본 아이디어는 decoder에서 출력단어를 예측하는 매 timestamp마다 encoder에서 전체 입력문장을 다시 참고하는 것이다. 이 때 모든 입력값들을 동일한 비중으로 참고하는 것이아니라, decoder에서 현재 timestamp를 넘겨주면 encoder에서 softmax함수를 사용하여 decoder의 추론에 도움이 되는 정도를 수치화하여 전달하여 주고 이 정보를 decoder는 사용하게 됩니다. 결과적으로 deocder는 출력단어를 더 정확하게 예측할 확률이 높아집니다.

Hard vs Soft Attention Attention은 크게 Hard와 Soft로 나누어지는데 가장 큰 차이점은 hidden state의 weight를 계산하는 function이 differentiable한가이다. Encoder의 hidden state를 differentiate하여 cost를 구하고 이를 사용하여 모델을 학습시키는 Soft Attention과 달리 Hard Attention은 randomness가 포함되어있기때문에 differentiable하지 않다. 따라서 주로 Reinforced Learning을 사용하여 모델을 학습시킨다.

Global vs Local Attention 만약 decoder에서 추론을 할때 모든 input을 보고 결정한다면 Global Attention이다.

반대로 deocder에서 추론을 할때 input의 특정 부분만을 보고 결정한다면 Local Attention이다

Self Attention Self Attention은 decoder의 state를 신경쓰지않고 input들 사이에서 각 timestamp때 어떤 input들에 더 높은 비중을 줘야될지를 결정한다. decoder없이 오로지 input들만으로 attention score를 계산하기때문에 구현이 쉽고 계산이 빠르며 decoder의 이전 state가 필요하지 않기때문에 한번에 다 계산해버릴 수 있다.

Reference:

Attention: https://wikidocs.net/22893

Seq2Seq: https://wikidocs.net/24996

Soft vs Hard Attention: https://stackoverflow.com/questions/35549588/soft-attention-vs-hard-attention

Self Attention: https://towardsdatascience.com/illustrated-self-attention-2d627e33b20a

더 읽어보기 »

[Machine Learning] Decision Tree 의사결정 나무

작성일 2019-12-06 | In MachineLearning

Decision Tree Learning은 데이터들의 모음으로부터 패턴을 추출하여 같은 규칙들 안에 들어가는 데이터들끼리 묶어 Decision Tree를 만들고, 이를 활용하여 새로운 데이터가 들어왔을때 데이터가 어떤 집합에 속하게 되는지를 판별함으로써 target을 예측하는 방법입니다.

위에 도표를 예시로 들자면, 가장 아래의 Leaf node를 먼저 살펴보겠습니다. Humidity <= 70의 노드에는 Play 2 Don’t Play 0로 되어있는데, 이는 sunny한 날에 Humidity가 70이하인 경우에는 2번의 경기가 열렸는데 모두 Play를 했다는 뜻입니다. 이를 확장한다면 위 도표의 Decision Tree는 총 10개의 경기를 Decision Tree로 분류한 것이며 1차적으로 (sunny, overcast, rain)으로 분류를 하고, sunny는 (humidity <= 70, humidity > 70)으로, rain은 (Windy TRUE, Windy False)로 분류하였습니다. 이렇게 Decision Tree를 원래가지고 있는 dataset(10개)로 생성해놓으면 새로운 경기 데이터를 예측할 때, sunny, overcast, rain 중 어디에 속하는지를 판단하고, 그 뒤에 Humidity 혹은 Windy로 판별을 한다면 새로운 경기가 Play할지 Don’t Play할지를 예측할 수 있게 되는 것입니다.

정리하자면, Decision Tree Learning은 기존의 dataset을 가지고, 분류가능한 특징을 가지고 classification tree를 생성하여 Decision Tree를 만들고, 이 tree를 사용하여 예측하는 것을 뜻합니다.

참고1 : https://en.wikipedia.org/wiki/Decision_tree_learning

참고2 : https://ratsgo.github.io/machine%20learning/2017/03/26/tree/

더 읽어보기 »
1 2 3 4 5
Junsoo Choi

Junsoo Choi

41 포스트
8 카테고리
46 태그
RSS
Github LinkedIn
© 2021 Junsoo Choi
Contact: cjsjyh@gmail.com