[Node.js] Node With IIS

Posted in 모바일/Javascript // Posted at 2016. 7. 19. 20:50
728x90

Node.js가 콘솔기반의 자체 HTTP 서버를 제공해 준다고는 하지만...

80포트 공유, 멀티 도메인과 서브 도메인 설정, HTTP 요청 로그, 동시 접속자 및 요청 큐 대기 상황, 웹 팜 및 웹 가든 구성, 디렉티브 권한 설정 등등 기존 IIS나 Apache 기반 환경에서 자주 사용했던 웹 서버 관련 사항은 어떻게 해야 하는가?

그리고 기존 IIS와 같은 웹 서버 환경에 추가하여 동시에 운용하고자 하는 경우는?

실무에서는 HTTP 통신서버의 비즈니스 기능 외에 다양한 옵션 및 환경설정이 요구되는데, Node.js가 이 부분을 모두 수용할 수 있는가?

솔직히 아직 Node.js를 복잡한 실무환경에서 제대로 적용해 보지 않은터라, 자세히 알지 못한다.

다만, IIS와 같은 나름 검증된 웹 서버에 호스팅되어 서비스 되면 좋겠다는 생각을 해 보던 차, 다음의 글을 발견했다.

http://www.sysnet.pe.kr/2/0/1556


한번 설정해 봐야 겠다.

이글을 보는 누군가, 기존 IIS 서버등에서 사용했던 많은 웹 서버 기능들을 Node.js 자체 환경에서도 여전히 안정적으로 사용가능한지 말해 줄 사람이 있다면, 댓글을 부탁드립니다.

 

728x90

웹은 기본적으로 '동일출저정책(Same Origin Policy, SOP)' 정책을  따른다.

이는 보안을 위한 기본정책으로, SOP는 하나의 출처(Origin)에서 로드된 문서나 스크립트가 다른 출처에 존재하는 리소스와 상호작용하지 못하도록 제약을 두는 것이다.

그런데, 간혹 이런 제약이 웹 응용프로그램을 만드는데 걸림돌이 되기도 한다.
Ajax 통신이 활발해 지고, 다른 사이트에 존재하는 Open API와 상호통신이 필요한 경우와 매쉬업(Mash-up)으로 새로운 2차 응용물을 개발하게 되면서.. 등등.. 이는 간혹 걸림돌이 된다.

근래 Node.JS로 서버를 만들고, Aptana Studio 자체 내장 웹서버로 몇 가지 테스트를 하고 있는데, 두 환경은 서로 다른 별도의 포트(Port)에서 웹을 동작시키기 때문에 여지없이 Cross Domain 문제가 발생한다.


호출하는 모든 웹 리소스를, Node.JS로부터 다운로드 받아서 실행하는 구조로 HTTP 서버를 구현하면 SOP 정책을 따를 수 있겠으나, 그렇지 못한 개발환경도 있을 터이고, 실제 서비스 환경에서는 더욱이 서로 다른 도메인(포트 포함)간 상호작용이 필요할 것이다.

SOP 정책을 우회하기 위해서는, 서버 측의 설정이 필요한데, 위 그림에서 친철히 안내하고 있는 것처럼 Access-Control-Allow-Origin 헤더, 즉 크로스 도메인으로 허용할 도메인 목록이 헤더로 제공되어야 한다.
이것을 CORS(Cross-Origin Resource Sharing)를 이용한 SOP 정책 우회라 한다.

Node.JS 환경에서 이 설정을 해보자.
다음과 같이, Node 기반 HTTP 서버의 응답헤더에 추가 해준다.

가장 중요한 설정은, Access-Control-Allow-Origin으로 허용할 도메인(포트)을 추가한다.
여러 도메인이 있을 경우, 중복해서 지정을 하거나 '*' 를 이용해 전체 허용도 가능하다.

var app = require("express")();

app.use(function(req, res, next) {
 res.header("Access-Control-Allow-Origin", "
http://127.0.0.1:8020");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    next();
});

app.get('/users', function(req, res) { 
 var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}]; 
    res.send(tempUsers);
});

app.listen(3002);
console.log("Listening on port 3002");

참고로 127.0.0.1:8020 도메인은, 필자의 Aptana Studio가 생성한 임의 주소이다.

이렇게 설정한 후, 클라이언트에서 요청하면 요청이 성공적으로 이뤄진다.

- 이 코드가 포함된 페이지는 127.0.0.1:8020 에 호스팅되어 동작한다.
<script>
window.onload = function(){
 var xhr = new XMLHttpRequest();
 xhr.onload = function(){
  console.log(xhr.response);  
 }; 
 xhr.open("GET", "http://127.0.0.1:3002/users");
 xhr.send();
}; 
</script>

그리고 언제나 그렇듯이 확장 라이브러리도 있다.
cors라는 이름의 Node.JS 확장인데, npm을 통해 설치하고 간단히 다음처럼 사용할 수 있다. 훨씬 간편하다.

var app = require("express")();

var cors = require('cors')();
app.use(cors);

그래도 언제나 보안은 중요한 문제이니, SOP 정책에 존중하면서, 필요시 아주 제한적으로 적용하는 것을 권장함.

 

'모바일 > Javascript' 카테고리의 다른 글

[Node.js] Node With IIS  (1) 2016.07.19
[AngularJS] 폼(form)과 유효성 검사  (0) 2016.07.18
[AngularJS] REST API 통신(with Node.js)  (0) 2016.07.15
[Node.js] socket.io를 활용한 웹채팅 구현  (5) 2016.07.15
[AngularJS] Route  (0) 2016.07.14

[AngularJS] REST API 통신(with Node.js)

Posted in 모바일/Javascript // Posted at 2016. 7. 15. 20:35
728x90

AngularJS는 원격 서버와의 HTTP 기반 통신을 위해 $http서비스를 제공한다.
$http는 브라우저의 XMLHttpRequest 객체나 JOSNP를 이용하여 원격 서버와 통신하기 위한 AngularJS의 서비스다.

이번 글에서는, $http서비스를 이용한 비동기 통신의 기본적 샘플을 구현해 보겠는데, 서버는 간단히 REST API 구현이 가능한 Node.JS를 사용한다.

* 서버 측 코드
- 먼저 Node.JS 기반으로 동작하는 HTTP 서버를 생성한다. express와 bodyParser 등 확장 라이브러리를 npm을 통해 먼저 설치해 둔다.

서버는 간단한 REST 기반 API 3개를 정의하고 있다.
사용자 목록(/users)과 단일사용자(/users/사용자id) 에 대한 GET API와 사용자등록(/users)을 위한 POST API로 구성되어 있다. 파일을 server.js로 저장하고, 쉘을 통해 server.js를 실행한다.
나머지 사항은 주석을 보면 이해될 정도로 심플하다.

var app = require("express")();
var url = require("url");

//post 데이터의 body 속성 접근 및 json파싱 위한 bodyParser 미들웨어 사용
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());

//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
  console.log("get:chatClient.html");
   //최초 루트 get 요청에 해대, 서버에 존재하는 client.html 파일 전송
   res.sendFile("client.html", {root: __dirname});
});
 
//GET API(http://127.0.0.1:3000/users), 전체사용자 목록
app.get('/users', function(req, res) {
   //임의의 사용자 리스트 생성(실제 환경에서는 db 등 연동 결과로 생성)
   var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}];
   //클라이언트에 반환
   res.send(tempUsers);
});

//GET API(http://127.0.0.1:3000/users/1), 특정 사용자 id를 지정하여 한명의 사용자 정보 호출
app.get('/users/:id', function(req, res) {
   //매개변수로 전달된 사용자 id 값 추출
   var id = req.params.id;
   //전달받은 사용자 id 그대로 다시 반환
   res.send([{id:id, city:'pusan'}]);
});

//POST API, 새로운 사용자 생성
app.post('/users', function(req, res){
   //POST 데이터로 넘어온 JSON 추출
   var postData = req.body;
   console.log(postData);
   //클라이언트에 그대로 다시 반환(실제 환경에서는 db 삽입 등 작업)
   res.send([postData]); 
});

//3000포트로 http 서버 리스닝 시키기
app.listen(3000);

console.log("Listening on port 3000");

 

* 클라이언트 측 코드
- 자바스크립트와 HTML을 분리하면 좋지만, 글 작성 편의를 위해 하나의 파일로 만든다. 그리고 서버코드에서 지정한 대로, client.html로 저장한다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
//원격 서버 URL 지정
var url = "http://127.0.0.1:3000/users/";

//AngularJS 메인모듈 생성
var app = angular.module('myApp', []);

app.controller('userController', function($http) {
 //현재 컨텍스트의 this 참조저장
 var userCtrl = this;

 //GET 요청
 //'http://127.0.0.1:3000/users' 또는 'http://127.0.0.1:3000/users/특정ID' 형태의 rest api 호출
 userCtrl.getUser = function(userID){
  //사용자 id가 매개변수로 넘어온 경우 url에 id 매개변수 붙이기
  var id = userID || "";

  $http.get(url + id)
  .then(function(response){
    //응답 성공 시 실행
    userCtrl.users = response.data; 
    userCtrl.response = response;        
  })
  .catch(function(error){
    //실패시 실행
    console.log('오류 발생');    
    userCtrl.response = error; 
  })
  .finally(function() {
    //성공, 실패 어떤 경우에도 실행
    console.log('finish getUser()');
   });
 };
 
 //POST 요청
 userCtrl.postUser = function(data){
  //post로 보낼 json 데이터 임의생성
  var postData = JSON.stringify({id:'newID', city:'newCity'});

  //http 요청객체 생성 
  var req = {
   method: 'POST',   
   url: url,
   headers: { 'Content-Type': 'application/json; charset=UTF-8'},
   data: postData
  };

  $http(req)
  .then(function(response){
     userCtrl.users = response.data;
     userCtrl.response = response;
  })
  .catch(function(error){
     console.log('오류 발생');    
     userCtrl.response = error; 
  })
  .finally(function() {
     console.log('finish postUser()');
  }); 
 };  
});
</script>

<body>
 <div ng-app="myApp" ng-controller="userController as userCtrl">
 <button ng-click='userCtrl.getUser();'>1.GET</button>
 <button ng-click='userCtrl.getUser("777");'>1.GET(using ID)</button>
 <button ng-click="userCtrl.postUser();">1.POST</button>
 <!-- 사용자 리스트 -->
 <ul>
    <li ng-repeat="user in userCtrl.users">
      {{ user.id + ', ' + user.city }}
    </li>
 </ul>
 <!-- 응답 객체 정보 출력 -->
 <p>
  response status: {{userCtrl.response}}
 </p> 
</div>
</body>
</html>

설명은 주석으로 충분한 듯 보이고, 실행화면은 다음과 같다.

- GET, http://127.0.0.1:3000/users/ 요청

 

- GET, http://127.0.0.1:3000/users/777 요청

 

- POST, http://127.0.0.1:3000/users/ 요청

 

728x90

재밌네... ㅎㅎ

별도의 비표준 플러그인 없이,  웹에서 채팅을 이렇게 쉽게 구현할 수 있는 시대라니...
6년전, 웹 소켓에 대해 나름 정리한 적이 있다.

 
이미 그 시대는 오래전(?)에 도래했건만, 개인적으로 방치해 두다가, 어제 밤 문득...

웹 소켓과 node.js를 사용해서 채팅 샘플을 구현해 보고 싶다는 생각이 스쳐 지나갔다.

socket.io라는 자바스크립트 라이브러리가 존재한다더니, 이건 뭐.. 다른건 볼 필요도 없네.

socket.io는 Node.js 기반으로 소켓 통신이 가능하도록 해 주는 확장 라이브러리인데, HTML5의 정식표준인 WebSocket을 지원하지만, 미지원 환경에 대한 호환성을 위해 Comet 같은 기술도 하나의 API로 추상화 시킨 양방향 통신 라이브러리이다.

socket.io는 WebSocket이 지원되지 않는 환경에서 실시간 양방향 통신을 구현하기 위해 다음과 같은 fallback 기술을 사용하고 있다.

- FlashSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling

기본적으로 Node.js 설치해 주고, express와 socket.io를 npm을 통해 다음과 같이 설치한다.
- npm install express
- npm install socket.io

이제 환경 설정은 끝이다. 자바스크립트 기반 개발환경은 이렇듯 심플하다. 내가 좋아하는 이유 중 주요한 팩터이다.

* socket.io를 이용한 웹 채팅 구현하기
먼저 다음과 같은 3개의 파일을 준비한다.

1. chatServer.js
- 채팅 서버 역할을 하는 자바스크립트 파일로, Node.JS에 의해 호스팅되며 socket.io 라이브러리를 통해 소켓 서버를 구현한다. 또한 부가적으로 http 리소스 요청에도 응답할 수 있도록 한다.

2. chatClient.js
- 채팅 서버와 통신을 하는 클아이언트 자바스크립트 파일로, 역시 socket.io 라이브러리를 사용하며 채팅을 위한 간단한 DOM 조작을 수행한다.

3. chatClient.html
- chatClient.js가 정의된 html 파일이며, 채팅창과 텍스트 박스로 간단히 구성된 뷰를 제공한다.

먼저 chatServer.js 파일의 코드부터 보자.

var app = require("express")();
var url = require("url");

//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
 console.log("get:chatClient.html");
 //최초 루트 get 요청에 대해, 서버에 존재하는 chatClient.html 파일 전송
 res.sendFile("chatClient.html", {root: __dirname});
});

//기타 웹 리소스 요청에 응답
app.use(function(req, res){
 var fileName = url.parse(req.url).pathname.replace("/","");
 res.sendFile(fileName, {root: __dirname});
 console.log("use:", fileName); 
});

//http 서버 생성
var server = require('http').createServer(app);
server.listen(3000);
console.log("listening at http://127.0.0.1:3000...");

//클로저를 사용해, private한 유니크 id를 만든다
var uniqueID = (function(){
 var id = 0;
 return function(){ return id++; };
})();

//서버 소켓 생성
var socket = require('socket.io').listen(server);
//소켓 Connection 이벤트 함수
socket.sockets.on('connection', function(client){
  //클라이언트 고유값 생성 
  var clientID = uniqueID();
  console.log('Connection: '+ clientID);
 
  //서버 receive 이벤트 함수(클라이언트에서 호출 할 이벤트)    
  client.on('serverReceiver', function(value){
    //클라이언트 이베트 호출     
    socket.sockets.emit('clientReceiver', {clientID: clientID, message: value});  
  });

});

다음으로 chatClient.js 소스이다.

window.onload = function(){ 
 //클라이언트 소켓 생성
 var socket = io.connect('ws://127.0.0.1:3000');

 //DOM 참조
 var div = document.getElementById('message');
 var txt = document.getElementById('txtChat');
 //텍스트 박스에 포커스 주기 
 txt.focus();
 
 //텍스트 박스에 이벤트 바인딩
 txt.onkeydown = sendMessage.bind(this); 
 function sendMessage(event){     
  if(event.keyCode == 13){
   //메세지 입력 여부 체크   
   var message = event.target.value;
   if(message){
     //소켓서버 함수 호출  
     socket.emit('serverReceiver', message);
     //텍스트박스 초기화
     txt.value = '';
   }
  }
 };
 
 //클라이언트 receive 이벤트 함수(서버에서 호출할 이벤트)
 socket.on('clientReceiver', function(data){  
   //console.log('서버에서 전송:', data);   
   //채팅창에 메세지 출력하기
   var message = '['+ data.clientID + '님의 말' + '] ' + data.message;
   div.innerText += message + '\r\n';
   //채팅창 스크롤바 내리기  
   div.scrollTop = div.scrollHeight;   
 });
};

 

 마지막으로 chatClient.html 파일이다.

<script src="chatClient.js"></script>
<script src="/socket.io/socket.io.js"></script>

<div id="message" style="background-color:#F5F5F5; width:400px; height:200px; OVERFLOW-Y:auto; word-wrap: break-word"></div>

<div id="chatInput">  
 메시지를 입력하세요 <input type='text' id='txtChat' name="txtChat"> 
</div>

 

* 실행흐름
1) 서버 실행
- Node의 쉘기능을 이용해 다음과 같이 chatServer.js를 실행한다. 실행하면 다음 그림과 같이 console 로그가 프롬프트 창에 찍힌다.

 

2) 클라이언트 실행
- 클라이언트에서는 단순히 브라우저로 지정된 url로 접근하기만 하면 된다.

브라우저로 chatServer.js 서버로 접속하면, 루트 get요청(http://127.0.0.1:3000)에 따른 chatClient.html 파일이 클라이언트로 다운로드 되고, 이 HTML파일이 브라우저에서 실행되면서 chatClient.js 파일에 대한 요청이 다시 이뤄져 자바스크립트 코드가 다운로드 되고 실행된다. 이 클라이언트 자바스크립트가 실행되어 소켓서버와 클라이언트는 서로 연결이 이뤄진다.

다음 그림은 브라우저에서 해당 url로 접근했을 때 서버측 로그화면이다.

 


* 결과화면

- 총 4개의 사용자가 접근하는 시나리오로 가정하여, 4개의 브라우저를 동시에 사용한다. 각 브라우저에서 해당 url로 접근하고 각자 채팅을 해 본다. 이때 각 클라이언트 구분은 서버측 코드에서처럼 임의의 순처적 고유값을 부여한 숫자로 사용한다.

다음 화면은 브라우저 4개를 띄우고, 채팅을 해본 모습니다. 혼자놀기에 좋다 ㅡ,ㅡ;


지금까지 알아본 샘플 채팅은 채팅 구현을 위한 가장 기본적이면서도 필수적으로 구현되어야 하는 것들만 다뤘다. 실제 업무에서 채팅을 구현한다면 성능과 안정성 등을 위해 다양한 방어코드, 추가기능 코드, 최적화 기법 등의 추가 작업이 필요할 것이다.

기본적인 흐름과 코드를 알았으니, 살을 붙여 나가면 될 일이다. 

'모바일 > Javascript' 카테고리의 다른 글

[Node.js] CORS 설정(Cross Domain 요청 허용)  (0) 2016.07.15
[AngularJS] REST API 통신(with Node.js)  (0) 2016.07.15
[AngularJS] Route  (0) 2016.07.14
[AngularJS] Directive  (0) 2016.07.13
[AngularJS] $scope과 controller-as 문법  (0) 2016.07.12