HTTP 서버 생성

HTTP 서버를 생성할 때는 각 HTTP 요청을 어떻게 처리해야 하는지 정의한 콜백 역할을 하는 익명함수가 createServer에 이자로 전달됩니다. 콜백 함수는 request 와 response 두개의 인자를 받습니다. 콜백이 실행될 때 HTTP 서버는 두개의 인자로 사용될 객체를 생성해 전달하게 되는데 이를 이용해 세부적인 요청사항에 대한 처리와 응답을 보내게 됩니다. server.js 파일에 아래의 코드를 추가해 줍니다.

 server.js

1
2
3
4
5
6
7
8
9
10
var server = http.createServer(function(request, response) {
  var filePath = false;
  if(request.url == '/') {
    filePath = 'public/index.html';
  } else {
    filePath = 'public' + request.url;
  }
  var absPath = './' + filePath;
  serveStatic(response, cache, absPath);
});
cs

 

▷HTTP 서버 시작

지금까지 HTTP 서버를 생성하는 코드를 작성했지만, 아직 서버를 구동하는데 필요한 로직을 추가하지 않았습니다. TCP/IP 3000번 포트로 요청받는 서버를 구동할 수 있게 로직을 추가해보겠습니다. 3000번 포트는 임의로 선택한 숫자입니다. 1024보다 큰 숫자중에서 사용되지 않는 번호를 선택해 포트로 사용할 수 있습니다.  server.js 파일에 다음의 코드를 추가해보겠습니다.

 server.js

1
2
3
server.listen(3000, function() {
  console.log("Server listening on port 3000.");
});
cs

 

이제 어플리케이션이 어떻게 동작하는지 보기 위해 다음 명령을 명령행 프롬프트(CMD)에 입력해 서버를 구동해보겠습니다.

먼저 CMD창을 열어줍니다. (윈도우키 + R 을 눌러 실행창에 cmd라고 입력 or 윈도우키를 누르고 명령 프롬프트 찾기) 

그리고 프로젝트 폴더로 이동한뒤에 node server.js 라고 입력해줍니다.

 

그럼 서버가 시작되었을텐데요 이 상태에서 웹 브라우저에서 'http://127.0.0.1:3000 ' or 'http://localhost:3000' 에 방문하면 404 오류 함수가 실행돼 "Error 404 : resource not found" 라는 메시지가 출력될 것입니다. 정적 파일을 처리하는 로직을 추가했지만, 아직 정적 파일을 추가하지 않았기 때문입니다. 실행 중인 서버는 Ctrl-C 를 눌러 중단할 수 있습니다.

 

 

▷HTML, CSS 파일 추가

이제 가장 먼저 추가할 정적 파일은 기본 HTML파일 입니다. public 디렉토리에 index.html 파일을 생성하고 코드를 작성하겠습니다.

HTML은 CSS 파일을 가져오고 어플리케이션 내용을 표시할 HTML div 요소를 만들며, 여러 개의 클라이언트 측 자바스크립트 파일을 불러옵니다.

불러온 자바스크립트 파일은 클라이언트 측의 Socket.IO 기능, 쉬운 DOM 조작을 위한 jQuery, 어플리케이션에 적합한 채팅 기능을 제공하기 위한 파일입니다.

index.html

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
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Chat</title>
    <link rel='stylesheet' href="stylesheets/style.css"></link>
  </head>
  <body>
    <div id='content'>
      <div id='room'></div>
      <div id="room-list"></div>
      <div id="messages"></div>
 
      <form action="" id="send-form">
        <input type="text" id="send-message"/>
        <input type="submit" id="send-button" value="Send"/>
        <div id="help">
          Chat commands:
          <ul>
            <li>Change nickname:<code>/nick [username]</code></li>
            <li>Join/create room:<code>join [room name]</code></li>
          </ul>
        </div>
      </form>
    </div>
    <script src="/socket.io/socket.io.js" type="text/javascript"></script>
    <script src="http://code.jquery.com/jquery-1.8.0.min.js" type="text/javascript"></script>
    <script src="/javascripts/caht.js" type="text/javascript'/"></script>
    <script src="javascripts/chat_ui.js" type="text/javascript"></script>
  </body>
</html>
cs

 

10행 : 현재 채팅방 이름을 표시할 div

11행 : 참여할 수 있는 채팅방 목록을 표시할 div

12행 : 채팅 메시지를 표시할 div

15행 : 사용자가 명령어와 메시지를 입력하라 Form 구성요소

 

 

다음으로 추가할 파일은 어플리케이션의 CSS 스타일을 정의한 파일입니다. public/stylesheets 디렉토리에 style.css 파일을 만들어줍니다.

style.css

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
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
{
  color: #00B7FF;
}
#content {
  width: 800px;
  margin-left: auto;
  margin-right: auto;
}
#room {
  background-color: #ddd;
  margin-bottom: 1em;
}
#messages {
  width: 690px;
  height: 300px;
  overflow: auto;
  background-color: #eee;
  margin-bottom: 1em;
  margin-right: 10px;
}
 
cs

 

10행 : 어플리케이션은 800픽셀 너비에 가로로 중앙 정렬

15행 : 현재 참여하고 있는 채팅방 이름을 표시하는 영역의 CSS

19행 : 메시지 표시 영역은 가로 690픽셀, 세로 300픽셀 크기로 지정

 

HTML과 CSS작성이 완료되면 어플리케이션을 실행해 웹 브라우저에서 확인해봅니다. 

 

 

어플리케이션의 기능은 아직 구현되지 않았지만, 정적 파일이 서비스되고 기본적인 외형을 갖췄습니다. 이제 서버측에서 채팅 메시지를 전달하는 부분을 만들어 보겠습니다.

 

▷Socket.IO를 이용한 채팅 관련 메시지 처리방법

1부에서 말했던 필수기능 3가지 중 첫번재 항목인 정적 파일 서비스를 완료했습니다. 이제 두번째 항목인 브라우저와 서버 간 통신을 시작할 차례입니다.

최신 브라우저는 웹소켓을 사용해 브라우저와 서버 사이의 통신을 처리합니다.

Socket.IO는 노드와 클라이언트 측 자바스크립트 모두에서 사용하는 웹소켓과 그 외 다른 전송 방식을 위한 추상화된 계층을 제공합니다. Socket.IO는 웹소켓이 브라우저에 구현돼 있지 않더라도 같은 API를 사용할 수 있도록 적절한 전송 방식으로 대체합니다.

▶ Socket.IO가 제공하는 가상 채널을 이용하면 접속한 모든 사용자에게 메시지를 뿌리지 않고 특정 채널을 구독하는 사용자에게만 메시지를 전송할 수 있습니다. 이 기능을 이용하면 채팅 어플리케이션의 채팅방 기능을 매우 간단하게 구현할 수 있습니다.

그리고 Socket.IO는 이벤트 발생자의 유용함을 보여주는 좋은 예입니다. 이벤트 발생자는 원래 편리하게 비동기 롲기을 관리하기 위한 디자인 패턴입니다.

 

 

▷Socket.IO 서버 구성

다음 두 줄을 server.js 에 추가해줍니다. 첫번째 줄은 서버 측에서 Socket.IO 기반으로 대화 기능에 대한 로직을 제공하는 커스텀 모듈을 가져옵니다.

다음 줄은 Socket.IO 서버 기능을 시작합니다. 이미 정의한 HTTP 서버를 이용하므로 같은 TCP/IP 포트를 공유할 수 있습니다.

server.js

1
2
var chatServer = require('./lib/chat_server');
chatServer.listen(server);
cs

이제 lib디렉토리에 chat_server.js 파일을 생성하고 다음 변수 선언문을 가장 앞에 추가합니다. 이 선언문은 Socket.IO의 사용과 상태를 정의하는 여러 변수의 초기화와 관련된 내용입니다.

chat_server.js

1
2
3
4
5
6
7
var socketio = rerquire('socket.io');
var io;
var guestNumber = 1;
var nickNames = {};
var namesUsed = [];
var currentRoom = {};
 
cs

 

▷연결 맺기 로직

이제 로직을 추가해 서버 함수인 listen을 정의합니다. 이 함수는 server.js에서 호출하며 Socket.IO 서버를 시작하고 Socket.IO가 콘솔에 출력하는 로깅을 제한하며 유입되는 연결을 처리하는 방법을 담고있습니다. 연결 처리 로직은 수많은 헬퍼 함수를 호출하며 chat_server.js에 바로 추가할 수 있습니다.

chat_server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
exports.listen = function(server) {
  io = socketio.listen(server);
  io.set('log level', 1);
 
  io.sockets.on('connection', function(socket){
    guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed);
    joinRoom(socket, 'Lobby');
 
    handleMessageBroadcasting(socket, nickNames);
    handleNameChangeAttempts(socket, nickNames, namesUsed);
    handleRoomJoining(socket);
 
    socket.on('rooms', function() {
      socket.emit('rooms', io.sockets.manager.rooms);
    });
    handleClientDisconnection(socket, nickNames, namesUsed);
  });
};
cs

 

2행 : 기존의 HTTP 서버에 피기백방식으로 SocketIO서버를 시작

5행 : 각 연결을 어떻게 처리해야 할지 정의

6행 : 사용자가 접속하면 손님 닉네임을 부여

9~11행 : 사용자의 메시지, 닉네임 변경, 채팅방 생성이나 변경에 관한 처리를 수행

13행 : 요청 시 이미 생성된 채팅방 목록을 사용자에게 제공

16행 : 사용자가 접속을 끊었을 때 관련 데이터 정리를 위한 로직을 정의

 

연결 처리에 관한 내용을 완료했다면 이제 어플리케이션의 요구 사항을 처리할 수 있는 각 헬퍼 함수를 추가해야 합니다.


출처 : Node.js 인 액션 

(3부에서 계속)

 

 

 


'Language > Node' 카테고리의 다른 글

2.다중 채팅방 만들기  (0) 2017.08.29
1.Node(노드)란? (2부)  (0) 2017.08.29
1.Node(노드)란?  (0) 2017.08.28


이번에는 단순한 이베트 주도 방식의 채팅 어플리케이션을 만들어 보면서 노드의 실용적인 측면을 살펴보도록 하겠습니다. 이번에 소개하는 상세한 내용 때문에 머리가 아프더라도 너무 걱정할 필요는 없습니다. 목적은 노드 개발에 대한 궁금증을 풀고 노드로 무엇을 할 수 있을지를 미리 본다고 생각하면 됩니다.

웹 어플리케이션을 개발해 본 경험이 있고 HTTP에 대한 기본적인 이해가 있으며 jQuery에 친숙하다는 전제하에 글을 쓰겠습니다.

먼저 진행 내용은 이렇습니다.

○ 어플리케이션의 동작 방식을 살펴본다.

○ 기술적인 요구사항을 살펴보고 어플리케이션 초기 설정을 진행합니다.

○ 어플리케이션에서 사용할 HTML, CSS, 클라이언트 측 자바스크립트를 서비스합니다.

○ Socket.IO를 이용해 채팅과 관련된 메시지를 처리합니다.

○ 어플리케이션 UI에 사용할 자바스크립트를 작성합니다.

 

▷어플리케이션 개요

만들 어플리케이션은 여러 사용자가 대화창에 메시지를 입력해 온라인에서 채팅할 수 있는 프로그램입니다. 한 사용자가 메시지를 입력하면 다른 모든 사용자에게 전송됩니다. 어플리케이션이 시작될 때 각 사용자에게 자동으로 닉네임이 주어지지만, 특정 명령어를 이용해서 닉네임을 변경할 수 있습니다. 닉네임을 변경하기 위한 채팅 명령어는 슬래시(/)로 시작합니다. 비슷한 방식으로 새로운 채팅방을 만들거나 기존 방에 참여할 때 채팅 명령어를 사용할 수 있습니다. 채팅방을 만들거나 참여하면 어플리케이션 상단의 가로 막대 영역에 새롭게 참여한 채팅방의 이름이 표시됩니다. 또한, 대화창 오른쪽에 있는 참여할 수 있는 모든 채팅방 목록에도 현재 채팅방의 정보가 표시 됩니다.

 

이 어플리케이션은 가장 단순한 기능만 있지만, 실시간 어플리케이션을 만드는데 필요한 주요 컴포넌트와 기반 기술을 소개하고 있습니다. 특히, 노드가 어떻게 정적 파일과 같은 일반적인 HTTP 데이터와 대화 내용과 같은 실시간성 데이터를 동시에 서비스 하는지 알 수 있습니다. 또한, 노드 어플리케이션의 구성 방식과 의존성 모듈 관리 방식도 보여줍니다.

 

▷어플리케이션 요구사항과 초기 설정

채팅 어플리케이션을 위해서는 다음과 같은 작업을 수행해야 합니다.

○ 정적 파일 서비스(HTML, CSS와 클라이언트 측 자바스크립트 등)

○ 서버에서 채팅과 관련된 메시지 처리

○ 사용자 브라우저에서 채팅과 관련된 메시지 처리

 

정적 파일을 서비스 하려면 노드에 포함된 HTTP 모듈을 사용해야 합니다. 또한, HTTP를 이용해 파일을 서비스 한다면 파일 내용뿐만 아니라 전송되는 파일의 종류도 포함하여 전송해야 합니다. 이 정보는 HTTP 헤더의 Contet-Type에 설정할 수 있으며 파일의 적절한 마임(MIME) 형식을 헤더에 추가해줍니다. 마임 형식은 mime이라는 서드파티 모듈을 사용해서 둘러 볼 수 있습니다.

mime : 전자 우편을 위한 인터넷 표준 포멧입니다. HTTP와 같은 통신 프로토콜에 사용되고 있습니다. 

(자세한 내용은 위키피디아 참고 : https://ko.wikipedia.org/wiki/MIME)

 

채팅과 관련되 메시지를 처리하기 위해 ajax를 이용해 서버로부터 데이터를 가져올 수 있습니다. 하지만 어플리케이션이 가능한 빨리 반응하게 하려면 기존의 ajax 방식을 사용해 메시지를 전송하는 방법을 피해야 합니다. ajax가 전송 체계로 사용하는 HTTP는 실시간 통신을 위해 설계된 것이 아닙니다. HTTP를 이용해 메시지를 전송하면 매번 새로운 TCP/IP 연결을 사용해야 합니다. 연결을 시작하고 종료하는데 시간이 소요되며 모든 요청마다 HTTP 헤더 정보를 보내야 하므로 전송해야 할 데이터 양도 많아지게 됩니다. 그러므로 HTTP보다는 실시간 통신을 지원하기 위해 설계된 양방향 경량 통신 프로토콜인 웹소켓을 이용하는 편이 더 좋습니다.

하지만 웹소켓을 사용하지 못하는 브라우저가 있을 때 다양한 대체 기능을 제공하는 인기 라이브러리인 Socket.IO를 활용해서 어플리케이션을 만들도록 하겠습니다. Socket.IO는 추가 코드나 설정 없이 대체 기능을 투명하게 처리합니다.

 

▷HTTP와 웹소켓 서비스

본격적으로 시작하기 전에 HTTP와 웹소켓을 동시에 처리하는 방법을 조금 알아보겠습니다. 이는 노드가 실시간 어플리케이션에 적절한 선택이 될 수 있는 이유이기도 합니다. 어플리케이션에서 채팅 메시지를 주고받을 때 ajax를 사용하지 않기로 했지만, 사용자의 브라우저에 그릴 화면을 보여줄 때 필요한 HTML, CSS, 클라이언트 측의 자바스크립트를 전송하려면 여전히 HTTP를 사용해야 합니다. 노드는 하나의 TCP/IP 포트를 이용해 HTTP와 웹소켓을 동시에 쉽게 서비스합니다. 노드 내부에는 HTTP 서비스를 제공하는 모듈이 있는데 익스프레스와 같이 노드의 내장 기능을 이용해 웹 서비스를 조금 더 쉽게 할 수 있도록 도와주는 서드파티 모듈이 많이 있습니다.

 

▷어플리케이션의 파일 구조 생성

어플리케이션을 제작하기에 앞서 프로젝트 디렉토리를 생성합니다. 주요 어플리케이션 파일이 디렉토리에 바로 저장됩니다.

 

 

 

lib : 서버 측 로직을 구현한 파일을 저장

public : 클라이언트 측과 관련된 파일을 저장

 ├ javascripts

 └ stylesheets

 

구조를 이렇게 만들었지만 노드는 특정 디렉터리 구조만을 고집하지 않습니다.

사용자가 생각하는 어떤방식으로도 어플리케이션 파일을 관리할수 있습니다.

디렉터리를 모두 생성했으면 어플리케이션의 의존성 모듈을 명시하는 방법을 자세히 알아보겠습니다.

 

 

▷의존성 모듈 명시

어플리케이션 의존성 모듈이란 어플리케이션에 필요한 기능을 사용하기 위해 설치해야 하는 모듈을 말합니다. 예를들어, MySQL 데이터베이스에 데이터를 저장하는 어플리케이션을 만들었다고 가정해보겠습니다. 노드 내부에는 MySQL에 접근할 수 있는 내장 모듈이 없으므로 이를 위한 서드파티 모듈을 따로 설치해야 하는데 이를 의존성 모듈로 볼 수 있습니다. 물론 의존성 모듈을 형식에 맞게 명시하지 않더라도 노드 어플리케이션을 만들 수 있지만, 의존성 모듈을 명시하는 것은 좋은 습관입니다. 의존성 모듈을 명시함으로써 다른 사용자가 어플리케이션을 사용하거나 여러 곳에서 프로그램을 실행해야 할 때 좀더 수월하게 사용 환경을 설정할 수 있습니다.

 

어플리케이션 의존성 모듈은 package.json 파일에 명시합니다. 이 파일은 항상 어플리케이션의 최상위 디렉터리에 있어야 합니다.

package.json 파일은 CommonJS 패키지 기술어 표준을 따르는 JSON 표현식을 사용하며 어플리케이션에 대한 설명을 담고 있습니다.

 

package.json

1
2
3
4
5
6
7
8
9
{
  "name" : "chatrooms",
  "version" : "0.0.1",
  "description" : "Minimalist multiroom chat server",
  "dependencies" : {
    "socket.io" : "~0.9.6",
    "mime" : "~1.2.7"
  }
}
cs

 

이 파일의 내용이 혼란스럽더라도 걱정할 필요는 없습니다. 그냥 단순히 어플리케이션의 정보를 담고 있는 파일이라고 생각하시면 됩니다.

name : 어플리케이션의 이름입니다.

version : 어플리케이션의 버전정보를 말해주는데 첫 버전이니 0.0.1로 설정했습니다.

description : 영어만 봐도 알 수 있듯이 어플리케이션의 대한 설명입니다.

dependencies : 의존성 모듈을 나타내는데요. 이 곳에 적힌 모듈을 확인해서 필요한 모듈을 설치할 수 있습니다. 반대로 필요한 모듈을 설치 받았을 때에 자동으로 이곳에 작성되기도 합니다.

 

그럼 이제 cmd창을 열어 우리가 만든 최상위 폴더로 이동해줍니다. cmd창에서 폴더를 이동하는 명령어는 cd 명령어 입니다.

최상위 폴더로 갔다면 npm install 이라는 명령어를 실행해봅니다.

 

디렉토리 구조를 만들고 의존성 모듈을 설치했으니 이제 어플리케이션 로직에 살을 붙일 준비가 되었습니다.

 

 

▷HTML, CSS, 클라이언트 측 자바스크립트 서비스

앞서 말했듯이 3가지 기능이 필요합니다.

○ 정적 파일 서비스(HTML, CSS와 클라이언트 측 자바스크립트 등)

○ 서버에서 채팅과 관련된 메시지 처리

○ 사용자 브라우저에서 채팅과 관련된 메시지 처리

 

먼저 정적파일을 서비스하기 위한 로직을 만들어 보겠습니다. 그리고 정적 HTML과 CSS파일을 추가하겠습니다.

정적 파일 서버를 생성하려면 노드에 내장된 몇가지 기능과 함께 파일의 MIME 타입을 판단할 수 있는 서드파티 mime모듈을 이용합니다.

가장 먼저 할 일은 프로젝트 최상위 폴더에 server.js 파일을 생성합니다.

이 파일은 노드의 HTTP 관련 기능과 파일시스템 연동, 파일 경로와 관련된 기능, 그리고 MIME 타입 판단 기능을 사용할 수 있게끔 합니다.

cache 변수는 캐시 파일 데이터를 다루는데 사용합니다.

server.js

1
2
3
4
5
var http = require('http'); 
var fs = require('fs');
var path = require('path');
var mime = require('mime');
var cache = {};
cs

 

1행 : HTTP 서버와 클라이언트 기능을 제공하는 내장 http 모듈

2행 : 파일시스템 관련 기능을 제공하는 fs 모듈 

3행 : 파일시스템 경로 관련 기능을 제공하는 내장 path모듈

4행 : 파일명 확장자 기반의 MIME 타입 추론 기능을 제공하는 외부 mime 모듈

5행 : 캐시된 파일의 내용이 저장되는 캐시 객체

 

다음으로 할 일은 정적 HTTP 파일 서비스에 필요한 세개의 헬퍼 함수를 작성하는 것입니다.

첫번째는 요청한 팡리이 존재하지 않을 때 404 오류를 전송하는 부분입니다. server.js 에 해당 함수를 추가해 보겠습니다.

1
2
3
4
5
function send404(response) {
  response.writeHead(404, {'Content-Type':'text/plain'});
  response.write('Error 404: resource not found.');
  response.end();
}
cs

 

두번째는 파일 데이터를 서비스하는 함수입니다. 먼저 적절한 HTTP 헤더를 작성한 다음 파일의 내용을 전송합니다. server.js에 작성합니다.

1
2
3
4
function sendFile(response, filePath, fileContents) {
  response.writeHead(200, {'Content-Type' : mime.lookup(path.basename(filePath))});
  response.end();
}
cs

 

파일시스템보다는 메모리 저장 장치(RAM)의 접근 속도가 더 빠릅니다. 이런 이유로 노드 어플리케이션에서는 최근에 사용한 데이터를 일반적으로 메모리에 캐싱합니다. 이번 채팅 어플리케이션에서는 정적 파일을 처음 디스크에서 읽을 때를 제외하고는 메모리에 캐시해 사용할 것입니다.

다음 함수는 캐시에 파일이 존재하는지 판단하여 캐시에 있다면 바로 서비스하고 캐시에 없다면 디스크에서 읽어와서 서비스합니다.

만약 어디에도 파일이 존재하지 않으면 응답으로 HTTP 404 오류를 반환합니다. 함수는 server.js에 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function serveStatic(response, cache, absPath) {
  if (cache[absPath]) {
    sendFile(response, absPath, cache[absPath]);
  } else {
    fs.exists(absPath, function(exists) {
      if(exists) {
        fs.readFile(absPath, function(err, data) {
          if(err) {
            send404(response);
          } else {
            cache[absPath] = data;
            sendFile(response, absPath, data);
          }
        });
      } else {
        send404(response);
      }
    });
  }
}
cs

2행 : 파일이 메모리에 캐시돼 있는지 확인

3행 : 캐시에 존재하는 파일이면 바로 서비스

5행 : 파일 존재 여부 검사

7행 : 디스크에서 파일 읽기

12행 : 디스크에 저장된 파일 서비스

16행 : HTTP 404 오류 응답

 

출처 : Node.js 인 액션

2부에서 계속..

 

 

 

 


'Language > Node' 카테고리의 다른 글

2.다중 채팅방 만들기 (2부)  (0) 2017.09.19
1.Node(노드)란? (2부)  (0) 2017.08.29
1.Node(노드)란?  (0) 2017.08.28

+ Recent posts