XMLHttpRequest 는 서버로부터 XML 데이터를 요청하거나 전송받을때 사용된다.

페이지가 전부 로딩된 후에도 사용가능하므로 웹페이지 전체를 다시 로딩하지 않고 일부분만 갱신하는게 가능하다.

 

readyState 프로퍼티는 XMLHttpRequest 객체의 현재 상태를 나타낸다.

UNSENT (0) XMLHttpRequest 객체가 생성되었음
OPENED (1) open() 메소드가 성공적으로 실행됨
HEADERS_RECEIVED (2) 모든 요청에 대한 응답이 도착함.
LOADING (3) 요청한 데이터를 처리 중.
DONE (4) 요청한 데이터의 처리가 완료되어 응답할 준비가 되었음.

status 프로퍼티는 서버의 문서 상태를 나타낸다.

200 서버에 문서가 존재함.
404 서버에 문서가 존재하지 않음
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { //요청에 대한 응답을 받는 리스너. 완료요청만 잡고싶으면 onload로 대체가능
  if (xhr.readyState === xhr.DONE) { //요청 완료
    if (xhr.status === 200 || xhr.status === 201) { //HTTP 상태코드가 200, 201 이면 성공
      console.log(xhr.responseText);
    } else {
      console.error(xhr.responseText);
    }
  }
};

xhr.open('GET', 'api 주소'); // 메소드와 주소 설정
xhr.send(); // 요청 전송
xhr.setRequestHeader('Content-Type', 'application/json'); // 컨텐츠타입을 json으로

setRequestHeader를 통해 보내는 데이터의 유형을 명시할수 있다.

 

var xhr = new XMLHttpRequest();
var formData = new FormData(); //formData객체. 이미지나 파일도 담을수 있다.
formData.append('name', 'tester');
formData.append('hobby', 'coding');
xhr.onload = function() {
  if (xhr.status === 200 || xhr.status === 201) {
    console.log(xhr.responseText);
  } else {
    console.error(xhr.responseText);
  }
};
xhr.open('POST', 'api 주소');
xhr.send(formData); //자동으로 Content-type 이 multipart/form-data가 된다.

'공부 > Node.js' 카테고리의 다른 글

REST API  (0) 2019.04.03
이벤트  (0) 2019.04.02
쿠키와 세션  (1) 2019.04.02
express  (0) 2019.03.08
로그 - winston 모듈  (0) 2019.03.08

요청은 주소를 통해 들어오며 html을 요청하는것 외에 세션저장 같은 동작을 취하길 요구 할수있다. 그러므로 서버가 이해하기 쉬운 주소를 사용하면 좋은데 여기서 REST API가 쓰인다.

 

REST API는 Representational State Transfer의 약어로 네트워크 구조의 한 형식이다.

서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 방법을 가리킨다.

주소는 의미를 명확히 전달하기 위해 명사로 구분되며 /user이면 사용자 정보에 관련된 자원을 요청하는 것이고, /post 라면 게시글에 관련된 자원을 요청하는 것이라고 추측할 수 있다.

 

주소 외에도 HTTP 요청 메서드라는 것을 사용하는데 폼 데이터를 전송할 때 GET 또는 POST 메서드 등을 지정하는것이 요청 메서드다.

GET 서버 자원을 가져오고자 할 때 사용한다. 요청의 본문에 데이터를 넣지 않는다. 데이터를 서버로 보내야 한다면 쿼리스트링을 사용한다.
POST 서버에 자원을 새로 등록하고자 할 때 사요한다. 요청의 본문에 새로 등록할 데이터를 넣어 보낸다.
PUT 서버의 자원을 요청에 들어 있는 자원으로 치환하고자 할때 사용한다. 요청의 본문에 치환할 데이터를 넣어 보낸다.
PATCH 서버 자원의 일부만 사용하고 할 때 사용한다. 요청의 본문에 일부 수정할 데이터를 넣어 보낸다.
DELETE 서버의 자원을 삭제하고자 할 때 사용한다.

주소 하나가 여러개의 요청 메서드를 가질 수 있으며

GET 메서드의 /user 주소로 요청을 보내면 사용자 정보를 가져오는 요청이라는 것을 알 수 있고

POST 메서드의 /user 주소로 요청을 보내면 새로운 사용자를 등록하려 한다는 것을 알 수 있다.

이렇게 주소와 메서드만보고 요청의 내용을 명확하게 알아볼 수 있다는것이 장점이다.

또한 GET 메서드 같은 경우에는 브라우저에서 캐싱할 수도 있어서 같은 주소의 GET 요청을 할 때 서버에서 가져오는 것이 아니라 캐시에서 가져올 수 있다. 이렇게 캐싱이되면 성능이 좋아진다.

 

HTTP 프로토콜을 사용하면 클라이언트가 누구든 상관없이 서버와 소통할 수 있다. iOS, 안드로이드, 웹이 모두 같은 주소로 요청을 보낼 수 있다. 즉, 서버와 클라이언트가 분리되어 있다는 뜻이다. 이렇게 서버와 클라이언트를 분리하면 추후에 서버를 확장할때 클라이언트에 구애되지 않아서 좋다.

 

- client -

function getUser() { // 로딩 시 사용자 가져오는 함수
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    if (xhr.status === 200) {
      var users = JSON.parse(xhr.responseText);
      var list = document.getElementById('list');
      list.innerHTML = '';
      Object.keys(users).map(function (key) {
        var userDiv = document.createElement('div');
        var span = document.createElement('span');
        span.textContent = users[key];
        var edit = document.createElement('button');
        edit.textContent = '수정';
        edit.addEventListener('click', function () { // 수정 버튼 클릭
          var name = prompt('바꿀 이름을 입력하세요');
          if (!name) {
            return alert('이름을 반드시 입력하셔야 합니다');
          }
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('PUT', '/users/' + key);
          xhr.setRequestHeader('Content-Type', 'application/json');
          xhr.send(JSON.stringify({ name: name }));
        });
        var remove = document.createElement('button');
        remove.textContent = '삭제';
        remove.addEventListener('click', function () { // 삭제 버튼 클릭
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('DELETE', '/users/' + key);
          xhr.send();
        });
        userDiv.appendChild(span);
        userDiv.appendChild(edit);
        userDiv.appendChild(remove);
        list.appendChild(userDiv);
      });
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('GET', '/users');
  xhr.send();
}
window.onload = getUser; // 로딩 시 getUser 호출
// 폼 제출
document.getElementById('form').addEventListener('submit', function (e) {
  e.preventDefault();
  var name = e.target.username.value;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    if (xhr.status === 201) {
      console.log(xhr.responseText);
      getUser();
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('POST', '/users');
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.send(JSON.stringify({ name: name }));
  e.target.username.value = '';
});

 

- server -

const http = require('http');
const fs = require('fs');

const users = {};

http.createServer((req, res) => {
  /*서버에 자원 요청, 사용자 목록*/
  if (req.method === 'GET') {
    if (req.url === '/') {
      return fs.readFile('./restFront.html', (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    } else if (req.url === '/about') {
      return fs.readFile('./about.html', (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    } else if (req.url === '/users') {
      return res.end(JSON.stringify(users));
    }
    return fs.readFile(`.${req.url}`, (err, data) => {
      if (err) {
        res.writeHead(404, 'NOT FOUND');
        return res.end('NOT FOUND');
      }
      return res.end(data);
    });
  }
  /*서버에 데이터를 담은 form을 제출*/
  else if (req.method === 'POST') {
    if (req.url === '/users') {
      let body = '';
      req.on('data', (data) => {
        body += data;
      });
      return req.on('end', () => {
        console.log('POST 본문(Body):', body);
        const { name } = JSON.parse(body);
        const id = +new Date();
        users[id] = name;
        res.writeHead(201);
        res.end('등록 성공');
      });
    }
  }
  /*서버에 데이터 수정을 요청*/
  else if (req.method === 'PUT') {
    if (req.url.startsWith('/users/')) {
      const key = req.url.split('/')[2];
      let body = '';
      req.on('data', (data) => {
        body += data;
      });
      return req.on('end', () => {
        console.log('PUT 본문(Body):', body);
        users[key] = JSON.parse(body).name;
        return res.end(JSON.stringify(users));
      });
    }
  } 
  /*서버에 데이터 삭제를 요청*/
  else if (req.method === 'DELETE') {
    if (req.url.startsWith('/users/')) {
      const key = req.url.split('/')[2];
      delete users[key];
      return res.end(JSON.stringify(users));
    }
  }
  res.writeHead(404, 'NOT FOUND');
  return res.end('NOT FOUND');
})
  .listen(8085, () => {
    console.log('8085번 포트에서 서버 대기중입니다');
  });

'공부 > Node.js' 카테고리의 다른 글

XMLHttpRequest  (0) 2019.04.03
이벤트  (0) 2019.04.02
쿠키와 세션  (1) 2019.04.02
express  (0) 2019.03.08
로그 - winston 모듈  (0) 2019.03.08
const EventEmitter = require('events');

const myEvent = new EventEmitter();
myEvent.addListener('event1', () => {
  console.log('이벤트 1');
});
myEvent.on('event2', () => {
  console.log('이벤트 2');
});
myEvent.on('event2', () => {
  console.log('이벤트 2 추가');
});

myEvent.emit('event1');
myEvent.emit('event2');

myEvent.once('event3', () => {
  console.log('이벤트 3');
});
myEvent.emit('event3');
myEvent.emit('event3');

myEvent.on('event4', () => {
  console.log('이벤트 4');
});
myEvent.removeAllListeners('event4');
myEvent.emit('event4');

const listener = () => {
  console.log('이벤트 5');
};
myEvent.on('event5', listener);
myEvent.removeListener('event5', listener);
myEvent.emit('event5');

console.log(myEvent.listenerCount('event2'));
on(이벤트명, 콜백) 이벤트 이름과 이벤트 발생시의 콜백을 연결해준다. 이렇게 연결하는 동작을 이벤트 리스닝이라고 부른다. 이벤트 하나에 이벤트 여러개를 달아줄 수도 있다.
addListener(이벤트명, 콜백) on과 기능이 같다.
emit(이벤트명) 이벤트를 호출하는 메서드. 이벤트 이름을 인자로 넣어주면 미리등록해줬던 이벤트 콜백이 실행된다.
once(이벤트명, 콜백) 이벤트에 연결된 모든 이벤트 리스너를 제거한다.
removeAllListeners(이벤트명) 이벤트에 연결된 모든 이벤트 리스너를 제거한다.
removeLisener(이벤트명, 리스너) 이벤트에 연결된 리스너를 하나씩 제거한다.
off(이벤트명, 콜백) 노드 10 버전에서 추가된 메서드. removeListener과 기능이 같다.
ListenerCount(이벤트명) 현재 리스너가 몇 개 연결되어 있는지 확인한다.

 

'공부 > Node.js' 카테고리의 다른 글

XMLHttpRequest  (0) 2019.04.03
REST API  (0) 2019.04.03
쿠키와 세션  (1) 2019.04.02
express  (0) 2019.03.08
로그 - winston 모듈  (0) 2019.03.08

쿠키란 인터넷 사용자가 어떠한 웹 사이트를 방문할 경우 그 사이트가 사용하고 있는 서버를 통해 인터넷 사용자의 컴퓨터에 설치되는 작은 기록 정보 파일을 의미한다.

 

쿠키는 서버와 클라이언트에서 모두 저장하고 사용 가능하며, 일정 기간 동안 데이터를 저장할 수 있기 때문에 로그인 상태를 일정 시간 유지해야 하는 웹 사이트에서 사용한다. 또한 이 기록 파일에 담긴 정보는 인터넷 사용자가 같은 웹사이트를 방문할 때마다 읽히고 수시로 갱신된다.

 

이때 민감한 개인정보를 쿠키에 넣어두는것은 적절하지 못하다. 쿠키는 생각보다 쉽게 노출되며(개발자도구의 Application 탭) 또한, 조작될 위험도 있다.

그러므로 세션을 이용해서 서버가 사용자 정보를 관리하도록 해야한다.

const http = require('http');
const fs = require('fs');
const url = require('url');
const qs = require('querystring');

const parseCookies = (cookie = '') =>
  cookie
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

const session = {};

http.createServer((req, res) => {
  const cookies = parseCookies(req.headers.cookie);
  if (req.url.startsWith('/login')) {
    const { query } = url.parse(req.url);
    const { name } = qs.parse(query);
    const expires = new Date();
    expires.setMinutes(expires.getMinutes() + 5);
    const randomInt = +new Date();
    session[randomInt] = {
      name,
      expires,
    };
    res.writeHead(302, {
      Location: '/',
      'Set-Cookie': `session=${randomInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
    });
    res.end();
  } else if (cookies.session && session[cookies.session].expires > new Date()) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(`${session[cookies.session].name}님 안녕하세요`);
  } else {
    fs.readFile('./server4.html', (err, data) => {
      if (err) {
        throw err;
      }
      res.end(data);
    });
  }
})
  .listen(8084, () => {
    console.log('8084번 포트에서 서버 대기중입니다!');
  });

 

http.createServer((req, res) => {
  const cookies = parseCookies(req.headers.cookie); //요청의 헤더에서 쿠키를 추출
  if (req.url.startsWith('/login')) { //요청의 url이 /login으로 시작할 경우. login 시도
    const { query } = url.parse(req.url); //요청에서 queryString을 추출
    const { name } = qs.parse(query); //queryString에서 name의 값을 추출
    const expires = new Date(); //현재 시간이 담긴 Date객체 생성
    expires.setMinutes(expires.getMinutes() + 5); //5분후 만료된다.
    const randomInt = +new Date(); //세션 식별자로 쓰일 Date 객체
    session[randomInt] = {
      name,
      expires,
    };
    /*응답으로 보낼 페이지의 헤더. 상태코드가 302 이므로 다른 페이지로 임시 이동 한다.*/
    res.writeHead(302, {
      Location: '/', //이동할 페이지
      'Set-Cookie': `session=${randomInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`, //브라우저에 저장할 쿠키
    });
    res.end(); //응답 전송
  }
  /*브라우저에서 전송한 쿠키에 session이 담겨있고 세션이 만료되지 않았다면 true 아니면 false*/
  else if (cookies.session && session[cookies.session].expires > new Date()) {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); //응답으로 보낼 페이지의 헤더
    res.end(`${session[cookies.session].name}님 안녕하세요`); //코드를 담아 응답을 전송
  } else {
    fs.readFile('./server4.html', (err, data) => { //전송할 html 파일을 읽어서 전송
      if (err) {
        throw err;
      }
      res.end(data);
    });
  }
})
  .listen(8083, () => {
    console.log('8084번 포트에서 서버 대기중입니다!');
  });

 

쿠키에 이름을 담아서 보내는 대신 randomInt 라는 임의의 순자를 보냈다. 이것을 세션 아이디라고 하며 서버에 사용자 정보를 저장하고 클라이언트와는 세션 아이디로만 소통한다.

(세션 아이디는 꼭 쿠키를 사용해서 주고 받지 않아도 된다. 실제 배포용 서버에서는 세션을 위와 같이 변수에 저장하지 않는다. 서버가 멈추거나 재시작되면 메모리에 저장된 변수가 초기화되고 또한 서버의 메모리가 부족하면 세션을 저장하지 못하는 문제도 생기기 때문이다. 그래서 보통은 데이터베이스에 넣어둔다.)

 

그러므로 사용자의 이름과 만료시간 같은 민감한 정보는 session이라는 객체에 대신 저장한다.

cookie.session이 있고 만료기한을 넘기지 않았다면 session 변수에서 사용자 정보를 가져와서 사용한다.

이러한 방식을 세션이라 한다.

쿠키명=쿠키값 기본적인 쿠키의 값. ex) name=tester
Expires=날짜 만료기한. 이 기한이 지나면 쿠키가 제거된다. 기본값은 클라이언트가 종료될때까지
Max-age=초 Expires와 비슷하지만 날짜 대신 초를 입력한다. 해당 초가 지나면 쿠키가 제거된다. Expires보다 우선한다.
Domain=도메인명 쿠키가 전송될 도메인을 특정한다. 기본값은 현재 도메인이다.
Path=URL 쿠키가 전송될 URL을 특정할 수 있다. 기본값은 '/'이고 이 경우 모든 URL에서 쿠키를 전송할 수 있다.
Secure HTTPS일 경우에만 쿠키가 전송된다.
HttpOnly 설정 시 자바스크립트에서 쿠키에 접근할 수 없다. 쿠키 조작을 방지하기 위해 설정하는 것이 좋다.
/*
쿠키 예시
'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`
*/
const parseCookies = (cookie = '') => //쿠키 추출
  cookie 매개변수로 받은 cookie
    .split(';') //; 를 기준으로 쿠키들을 나눈것을 반환한다.
    .map(v => v.split('=')) //위의 split에서 넘겨받은 쿠키 문자열을 = 를 기준으로 나누어 [키, 값]의 형태로 배열에 담아 반환한다.
    .reduce((acc, [k, v]) => { //위의 map에서 넘겨받은 배열을 acc에 누적시킨다.
      acc[k.trim()] = decodeURIComponent(v); //acc에 공백을 지운 키와 디코드한 값을 누적
      return acc; //누적값을 return
    }, {});

 

'공부 > Node.js' 카테고리의 다른 글

REST API  (0) 2019.04.03
이벤트  (0) 2019.04.02
express  (0) 2019.03.08
로그 - winston 모듈  (0) 2019.03.08
템플릿 엔진 모듈 - ejs, pug 모듈  (0) 2019.03.07

express 모듈은 node.js로 웹 서버를 개발할 때 가장 많이 쓰이는 모듈이다. 물론 http 모듈을 통해 웹 서버를 개발해도 되지만 express 모듈은 훨씬 쉽고 편하게 개발할 수 있게 해준다.

 

const express = require('express');

const app = express();

app.use((request, response) => { //get() 함수를 통해 URI로 호출되면 해당 로직이 실행된다.
response.send('Hello express module');
});

app.listen(3000, () => { //서버를 동작시킴
console.log('Server is running port 3000!');
});

 

Express-generator

* 익스프레스 프레임워크는 많은 의존성 패키지를 요구하는데 이것을 찾아 설치하기는 조금 어렵다. 그러므로 package.json을 만들어주고 기본 폴더 구조까지 잡아주는 패키지가 만들어졌는데 그것이 Express-generator 다.

해당 패키지는 콘솔 명령어로 실행 하므로 npm 전역 설치가 필요하다.

npm install -g express-generator

 

익스프레스 프로젝트를 생성하려면 새로 프로젝트를 만들고자 하는 폴더로 이동해서

express <프로젝트 이름> 을 입력한다.

해당 명령어는 생성된 폴더 및 파일명을 출력해주며 다음에 입력해야할 명령어를 알려준다.

프로젝트 이름으로 생성된 폴더로 이동한후 npm i 를 입력하면 프로젝트 폴더내에 생성된 package.json 에 명시된 패키지들이 설치된다.

 

--view=pug

* 프로젝트에서 사용할 템플릿 엔진을 설정한다. ejs를 사용하고 싶다면 pug 대신 ejs를 입력하면 된다. 

 

생성된 폴더 구조

* 라우터를 컨트롤러라고 본다면 MVC(모델-뷰-컨트롤러) 패턴과 비슷하다. 

생성된 폴더 및 파일

app.js 서버 역할
bin/www

* 서버를 실행하는 스크립트로 http 모듈에 express 모듈을 연결하고 포트를 지정하는 역할을 맡는다.
* js 확장자가 붙어있지 않다.
* #!/usr/bin/env node 라는 주석이 첫 줄에 달려있는데. www 파일을 콘솔 명령어로 만들때 이 주석을 명령어로 쓸수있다.



public 외부(브라우저 등의 클라이언트)에서 접근 가능한 파일들을 모아두는 폴더. 이미지, 자바스크립트, css 파일들이 들어있다.
routes 주소별 라우터들을 모아둔 폴더. 서버의 로직이 담긴 파일은 이곳에 담긴다.
views 템플릿 파일을 모아둔 폴더. 화면에 보이기 위한 파일을 담는다.
models 데이터 파일을 담아두는 폴더.

bin/www

/**
 * Module dependencies.
 */
 
/*app, debug, http 모듈을 가져온다.*/
var app = require('../app');
var debug = require('debug')('test-project:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

/*
서버가 실행될 포트를 설정.
process.env 객체에 PORT 속성이 있다면 그 값을 사용하고 없다면 기본값으로 3000번 포트를 이용한다.
app.set(키, 값)을 사용해서 데이터를 저장할수있으며 app.get(키) 로 가져올 수 있다.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

/*http.createServer에 불러온 모듈을 넣어준다. app 모듈이 createServer 메소드의 콜백함수 역할을 한다.*/
var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

/*listen을 시작. http 웹 서버와 동일하지만 익스프레스는 콜백함수 부분을 조금 다르게 만든다.*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

/*express 패키지로 객체 생성. 이 변수에 각종 기능을 연결한다.*/
var app = express();

// view engine setup
/*app.set 메서드로 익스프레스 앱을 설정할 수 있다.*/
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

/*app.use를 사용해서 미들웨어를 연결한다.*/
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

/*app 객체를 모듈로 만들어 반환한다. 이것이 bin/www에서 사용된다.*/
module.exports = app;

package.json 을 보면 scripts에 start 속성이 있고 속성값으로 node ./bin/www 가 적혀있다. 따라서 npm run start 나 npm start 명령어로 서버를 실행할 수 있다.

 

 

 

- response -

const express = require('express');

const app = express();
 
/*

app.get('위치')를 지정할 수 있다.

send() 메소드에 객체로 입력했기 때문에 JSON 형식으로 출력된다.
*/
app.get('/', (request, response) => {
const result = [];
const multipleNumber = 9;
for (let i = 1; i < 5; i++) {
result.push({
number: `${multipleNumber}X${i}`,
multiple: multipleNumber * i,
});
}
response.send(result);
});
 
/*status() 메소드를 활용해 상태 코드를 전달한다.*/
app.get('/error', (request, response) => {
response.status(404).send('404 ERROR');
});

app.listen(3000, () => {
console.log('Server is running port 3000!');
});

 

response 객체의 메소드

 res.download() 

파일이 다운로드되도록 프롬포트 한다. 

 res.end()

응답 프로세스를 종료한다. 

 res.json()

JSON응답을 전송한다. 

 res.jsonp()

JSONP 지원을 통해 JSON 응답을 전송한다. 

 res.redirect()

요청의 경로를 재지정한다. 

 res.render()

템플릿을 랜더링한다. 

 res.send()

다양한 유형의 응답을 전송한다.(문자열 HTML, 객체 JSON, 배열 JSON의 형태로) 

 res.sendFile

파일을 전송한다. 

 res.sendStatus()

응답 상태 코드를 설정한 후 해당 코드를 문자열로 표현한 내용을 응답 본문으로 전송한다. 

 

 

 

- request -

// 요청 메서드
const express = require('express');

const app = express();

app.use((request, response) => {
/*요청 헤더를 추출. 웹 브라우저로 HTTP 요청시 User-Agent 속성값을 가지고 있다.*/
const agent = request.header('User-Agent');
 
const paramName = request.query.name; //매개변수 추출
const browserChrome = 'Hello Chrome';
const browserOthers = 'Hello Others';
 
/*request() 객체에 기본적으로 내장된 메소드*/
console.log(request.headers);

console.log(request.baseUrl);

console.log(request.hostname);
console.log(request.protocol);

 

if (agent.toLowerCase().match(/chrome/)) { //Chrome 브라우저인가?
response.write(`<div><p>${browserChrome}</p></div>`);
} else {
response.write(`<div><p>${browserOthers}</p></div>`);
}
response.write(`<div><p>${agent}</p></div>`);
response.write(`<div><p>${paramName}</p></div>`);
response.end();
});

 

app.listen(3000, () => {
console.log('Server is running port 3000!');
});

 

express 모듈의 request 객체를 이용하면 사용자가 요청(request)한 내용이 어떤 것인지 알수 있다.

request 객체를 활용하여 매개변수를 추출하고 헤더의 속성을 알아내 브라우저를 구분하여 페이지를 출력할 수 있다.

 headers 

요청 헤더의 추출

 Header() 

요청 헤더의 속성 지정 또는 추출 

 query

GET 방식으로 요청한 매개변수 확인 

 body

POST 방식으로 요청한 매개변수 확인 

 params

라우팅 매개변수 추출 

 

- 미들웨어 -

요청과 응답의 중간에 위치해 미들웨어라 불린다. 라우터나 에러 핸들러 같은 다른 로직을 실행해 요청과 응답을 조작할수있으며 기능을 추가하거나 나쁜 요청을 걸러낼수 있도록 분리된 함수이다.

 

- use -

/*
app.use 메서드의 인자로 들어 있는 함수가 미들웨어이다. 
미들웨어는 use 메서드로 app에 장착되며 장착된 순서대로 실행된후 라우터에서 클라이언트로 응답을 보낸다.
*/
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// 404 처리 미들웨어
app.use(function(req, res, next) {
  /*
  http-errors(createError) 패키지가 404 에러를 만들어내고
  이 에러를 next에 담아 아래 에러 핸들러로 보낸다.
  */
  next(createError(404));
});

// 에러 핸들러. 일반적으로 미들웨어 중 제일 아래에 위치하고 위에있는 미들웨어에서 발생하는 에러를 받아서 처리한다. 
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

 

- 커스텀 미들웨어 -

// 미들웨어 개요
const express = require('express');

const app = express();

app.use((request, response, next) => {
console.log('첫번째 미들웨어에 요청');
request.user1 = '철수';
next();
});

app.use((request, response, next) => {
console.log('두번째 미들웨어에 요청');
request.user2 = '영이';
next();
});

app.use((request, response, next) => {
console.log('세번째 미들웨어에 요청');
response.writeHead('200', { 'Content-Type': 'text/html;charset=utf8' });

response.write(`<div><p>${request.user1}</p></div>`);
response.write(`<div><p>${request.user2}</p></div>`);
response.end('<h1>express 서버에서 응답한 결과</h1>');
});

app.listen(3000, () => {
console.log('Server is running port 3000!');
});

* 위처럼 사용자가 미드웨어를 직접 만드는것도 가능하다. 단, 다음 미드웨어로 넘어가려면 next()를 호출해야한다.

* 만약 next를 넣지 않는다면 중간에 요청의 흐름이 끊겨버릴수 있다.

* next()는 인자에 따라 다른기능을 하기도 하는데

인자가 없으면 다음 미들웨어로 넘어가고

인자로 route를 넣으면 라우터 기능을 한다.

route외에 다른값을 넣으면 미들웨어나 라우터를 건너 뛰고 바로 에러 핸들러로 이동한다. 넣어준 값은 에러에 대한 내용으로 간주된다.

 

*아래처럼 하나의 use에 미들웨어를 여러 개 장착 할 수 있다. 순서대로 실행된다.

app.use('/', function(req, res, next) {
  console.log('첫 번째 미들웨어');
  next();
}, function(req, res, next) {
	console.log('두 번째 미들웨어');
    next();
}, function(req, res, next) {
      console.log('세 번째 미들웨어');
      next();
});

 

- static -

정적인 파일들을 제공해주는 미들웨어로 express에 내장되어있다.

app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser('secret code'));

함수의 인자로 정적 파일들이 담겨있는 폴더를 지정하면 되는데

public/stylesheets/styles.css 는 http://localhost:3000/stylesheets/styles.css 로 접근이 가능하다.

실제 서버의 폴더 경로와 요청 경로가 다르므로 외부인이 서버의 구조를 파악하기가 쉽지 않으며 이는 보안에 큰 도움이 된다.

// static 미들웨어
// express 모듈 불러오기
const express = require('express');

// express 객체 생성
const app = express();

app.use(express.static(`${__dirname}/multimedia`));
app.use((request, response) => {
response.writeHead('200', { 'Content-Type': 'text/html;charset=utf8' });
response.end('<img src="/newyork.jpg" width="100%"/>');
});

app.listen(3000, () => {
console.log('Server is running port 3000!');
});

 

app.use('/img', express.static(path.join(__dirname, 'public')));

위처럼 정적 파일을 제공할 주소를 지정할 수도 있다.

public 폴더 안에 abc.png 는 앞에 /img 경로를 붙인 http://localhost:3000/img/abc.png 주소로 접근 가능하다.

 

* static 미들웨어는 요청에 부합하는 정적 파일을 발견하면 응답으로 해당 파일을 전송한다. 이 경우 응답을 보냈으므로 다음에 나오는 라우터가 실행되지 않는다. 만약 파일을 찾지못했다면 요청을 라우터로 넘긴다.

* 이렇게 자체적으로 정적 파일 라우터 기능을 수행하므로 최대한 위쪽에 배치하는게 좋다.

 

- body parser -

POST나 PUT과 같은 요청을 처리할 때 사용자가 보낸 데이터를 해석해 request 객체의 body에 추가 및 할당해준다.

보통 폼 데이터나 AJAX 요청의 데이터를 처리한다.

 

express에 내장된 body-parser의 일부기능으로 다음과 같이 데이터 처리가 가능하지만

app.use(express.json());
app.use(express.urlencoded({ extended: false}));

JSON과 URL-encoded 외에 RAW, Text 형식의 본문을 추가로 해석하고싶을땐 body-parser를 사용해야한다.

Raw는 본문의 버퍼 데이터 일때, Text는 본문이 텍스트 데이터일 때 해석하는 미들웨어로 아래처럼 사용이 가능하다.

app.use(bodyParser.raw());
app.use(bodyParser.text());

JSON은 JSON 형식의 데이터 전달 방식이고, URL-encoded는 주소 형식으로 데이터를 보내는 방식이다.

보통 폼 전송이 URL-encoded 방식을 주로 사용한다.

// body parser 미들웨어

// 모듈 불러오기
const express = require('express');
const bodyParser = require('body-parser');

// express 객체 생성
const app = express();

// application/x=www-form-urlencoded 파싱.
/*
{ extended: false } 이 옵션이 false이면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석하고
true 면qs 모듈을 사용하여 쿼리스트링을 해석한다.
*/
app.use(bodyParser.urlencoded({ extended: false }));

// application/json 파싱
app.use(bodyParser.json());

app.use(express.static(`${__dirname}/login`));

app.use((request, response) => {
const userId = request.body.userid || request.query.userid;
const userPassword = request.body.password || request.query.password;

response.writeHead('200', { 'Content-Type': 'text/html;charset=utf8' });
response.write('<h1>Login ID와 PW 결과 값 입니다.</h1>');
response.write(`<div><p>${userId}</p></div>`);
response.write(`<div><p>${userPassword}</p></div>`);
response.end(JSON.stringify(request.body, null, 2));
});

app.listen(3000, () => {
console.log('Server is running port 3000!');
});

ex)

JSON 형식 { name : 'tester', book: 'nodejs' } 를 본문으로 보내면 req.body에 그대로 들어가지만

URL-encoded 형식 name=tester&book=nodejs를 본문으로 보내면 req.body에 { name : 'tester', book: 'nodejs' } 가 들어간다.

 

* body-parser가 모든 본문을 해석해는 것은 아니다. multipart/form-data 같은 폼을 통해 전송된 데이터는 해석하지 못하며 이는 다른 모듈을 사용해서 해석해야한다.

 

- cookie-parser -

요청에 동봉된 쿠키를 해석해준다.

var cookieParser = require('cookie-parser');
app.use(cookieParser());

해석된 쿠키들은 req.cookies 객체에 들어간다.

ex) { name : 'tester' }

 

인자로 문자열을 넣어줄 경우 제공한 문자열로 서명된 쿠기가 되며 서명된 쿠키는 클라이언트에서 수정했을 때 에러가 발생하게 되므로 클라이언트에서 쿠키로 위험한행동을 하는 것을 방지 할 수 있다.

 

- morgan -

요청에 대한 정보를 콘솔에 기록해주는 미들웨어

var logger = require('morgan');
app.use(logger('dev'));

인자로 dev 대신 short, common, combined 등을 줄 수 있으며 인자에 따라 콘솔에 나오는 로그가 다르다.

dev 인 경우

GET / 200 51.267 ms - 1539 의미는 순서대로

HTTP요청(GET) 주소(/) HTTP상태코드(200) 응답속도(51.267ms) - 응답바이트(1539) 이다.

short나 dev는 개발시

common 이나 combined는 배포시에 많이 쓰인다.

 

- express-session -

세션 관리용 미들웨어로 로그인 등의 이유로 세션을 구현할 때 쓴다.

express-generator로 설치되지 않으므로 npm을 통해 별도로 설치해야한다.

 

express-session 1.5 버전 이전에는 내부적으로 cookie-parser를 사용하고 있어서 cookie-parser 미들웨어보다 뒤에 위치해야 했지만, 1.5버전 이후부터는 사용하지 않게 되어 순서가 상관 없어졌다. 만약 어떤 버전을 사용하고 있는지 모르겠다면 cookie-parser 미들웨어를 뒤에 놓는 것이 안전하다.

var logger = require('morgan');
var session = require('express-session');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

app.use(cookieParser('secret code'));
app.use(session({
  /* resave 요청이 왔을 때 세션에 수정사항이 생기지 않더라도 세션을 다시 저장할지에 대한 설정 */
  resave: false,
  /* 세션에 저장할 내역이 없더라도 세션을 저장할지에 대한 설정. 방문자를 추적할 때 사용된다. */
  saveUninitialized: false,
  /* cookie-parser의 비밀키와 같은 역할 */
  secret: 'secret code',
  /*
  세션관리 시 클라이언트에 쿠키를 보내는데 이를 세션쿠키라 부른다. 
  안전하게 쿠키를 전송하려면 쿠키에서명을 추가해야 하는데 쿠키를 서명하는데 secret의 값이 필요하다. 
  cookie-parser의 secret과 같게 설정해야한다.
  아래의 cookie 옵션은 세션 쿠키에 대한 설정으로
  maxAge, domain, path, expires, sameSite, httpOnly, secure 등 일반적인 쿠키 옵션이 모두 제공된다.
  store 라는 옵션도 있는데 세션을 데이터베이스에 저장할 수 있게 해준다. 데이터베이스는 보통 레디스가 자주 쓰인다.
  */
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));

- connect-flash -

일회성 메시지들을 웹 브라우저에 나타낼때 좋으며 express-generator로 설치되지 않으므로 npm을 통해 별도로 설치해야한다.

connect-flash 미들웨어는 cookie-parser 와 express-session을 사용하므로 이들보다 뒤에 위치해야한다.

var flash = require('connect-flash');

app.use(flash());

flash 미들웨어는 res 객체에 req.flash 메서드를 추가하며 req.flash(키, 값)으로 해당 키에 값을 설정하고 불러온다.

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

router.get('/flash', function(req, res) {
  req.session.message = '세션 메시지';
  req.flash('message', 'flash 메시지');
  res.redirect('/users/flash/result');
});

router.get('/flash/result', function(req, res) {
  res.send(`${req.session.message} ${req.flash('message')}`);
});

module.exports = router;

/users/flash 라우터로 GET 요청을 보내면 서버에서는 세션과 flash에 미시지를 설정하고 /users/flash/result 메시지로 리다이렉트 한다. 첫 번째 /users/flash/result에는 세션 메시지와 flash 메시지가 모두 보인다.

해당 메시지는 일회성이기 때문에 새로고침시 보이지 않는다. 이러한 성질을 이용해서 로그인 에러나 회원가입 에러 같은 일회성 경고 메시지를 보낼수 있다.

 

- Router -

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

app.use('/', indexRouter);
app.use('/users', usersRouter);

라우팅 미들웨어는 첫 번째 인자로 주소를 받아서 특정 주소에 해당하는 요청이 왔을때만 미들웨어가 동작하게 할 수도 있다. 주소가 /로 시작하면 routes/index.js를, /users로 시작하면 routes/users.js를 호출하라는 의미이다.

 

use 메서드는 모든 HTTP 메서드에 대해 요청 주소만 일치하면 실행되지만 get, post, put, patch, delete 같은 메서드는 주소뿐만 아니라 HTTP 메서드까지 일치하는 요청일 때만 실행된다.

/* HTTP 메서드는 상관없이 / 주소요청일 때 실행 */
app.use('/', function(req, res, next) { next(); }
/* GET 메서드 / 주소의 요청일 때 실행 */
app.get('/', function(req, res, next) { next(); }
/* POST 메서드 /data 주소의 요청일 때 실행 */
app.post('/', function(req, res, next) { next(); }

app.use 처럼 router 하나에 미들웨어를 여러 개 장착할 수도 있다. 라우터 로직이 실행되는 미들웨어 전에 로그인 여부 또는 관리자 여부를 체크하는 미들웨어를 중간에 넣을수있다.

router.get('/', middleware1, middleware2, middleware3);

 

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  /*클라이언트에 응답을 보냄. express가 응답 객체에 추가한 메서드로 템플릿 엔진을 사용하는 부분*/
  res.render('index', { title: 'Express' });
});

module.exports = router;
var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

router.get('/flash', function(req, res) {
  req.session.message = '세션 메시지';
  req.flash('message', 'flash 메시지'); //저장
  res.redirect('/users/flash/result');
});

router.get('/flash/result', function(req, res) {
  res.send(`${req.session.message} ${req.flash('message')}`); //추출
});

module.exports = router;

 

* 브라우저는 요청시 제한시간 동안 응답을 받을때까지 대기하므로 라우터에서는 요청에 대한 응답을 보내야한다.

* next 함수가 라우터에서 동작할때 매개변수로 'route'를 주면 연결된 나머지 미들웨어들을 건너뛴다. 대신 주소가 일치하는 다음 라우터로 넘어간다.

router.get('/', function(req, res, next) {
	next('route');
}, function(req, res, next) {
	console.log('실행 X');
    next();
}, function(req, res, next) {
	console.log('실행 X');
    next();
});

router.get('/', function(req, res) {
	console.log('실행 O');
    res.render('index', { title:'Express' });
}

그 외에도

router.get('/user/:id', function(req, res) {
	console.log(req.params, req.query);
});

처럼 :id를 쓰면 /users/1 같은 요청도 라우터에 걸리며 :id에 해당하는 부분을 req.params.id를 통해 조회할 수 있다.

주소에 쿼리스트링을 쓰면 쿼리스트링의 키-값 정보는 req.query 객체 안에 들어 있다.

 

ex) /users/123?limit=5&skip=10 이라는 주소의 요청이 들어오면

req.params와 req.query 객체는 { id: '123' } {limit : '5', skip: '10' } 이다.

 

단, 이러한 주소패턴을 사용하는 라우터는 일반 라우터보다 뒤에 위치해야한다. 다양한 라우터를 아우르는 와일드카드 역할을 하므로 일반 라우터보다는 뒤에 위치해야 다른 라우터를 방해하지 않는다.

 

응답 메서드는 send, sendFile, json, redirect, render를 주로 사용한다.

send 왠만한 모든 형식을 지원하며 버퍼 데이터나 문자열을 전송하거나 HTML 코드를 전송하기도 하며 JSON 데이터도 전송이 가능하다
ex) res.send(버퍼 or 문자열 or HTML or JSON)
sendFile 파일을 응답으로 보내주는 메서드이다.
ex) res.sendFile(파일 경로);
json JSON 데이터를 보내준다.
res.json(JSON 데이터);
redirect 응답을 다른 라우터로 보낸다.
ex) 로그인 완료 후 다시 메인 화면으로 돌아갈 때 res.redirect(메인 화면 주소) 를 하면 된다.
render 템플릿 엔진을 렌더링한후 결과를 전송한다.
ex) res.render('템플릿 파일 경로', { 변수 });

* 기본적으로 200 HTTP 상태코드를 응답하지만 (단, res.redirect는 302) status 메서도를 먼저 사용하면 직접 바꾸는게 가능하다.

res.status(404).send('Not Found')

* 하나의 요청에 대한 응답은 한 번만 보내야한다. 두 번 이상 보내면 에러가 발생한다.

* 라우터가 요청을 처리하지 못할 경우 404 HTTP 상태코드를 전송하는 에러 처리 미들웨어로 넘겨야한다.

// 404 처리 미들웨어
app.use(function(req, res, next) {
  next(createError(404));
});

// 에러 핸들러
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

'공부 > Node.js' 카테고리의 다른 글

이벤트  (0) 2019.04.02
쿠키와 세션  (1) 2019.04.02
로그 - winston 모듈  (0) 2019.03.08
템플릿 엔진 모듈 - ejs, pug 모듈  (0) 2019.03.07
크롤링 - cheerio, iconv-lite 모듈  (1) 2019.03.07

어플리케이션을 운영하다보면 로그를 남기고 보관하는게 중요해진다.


- winston -

npm install winston -save

npm install winston-daily-rotate-file -save

npm install moment -save


로거(Logger)란 로그를 출력하는 객체를 뜻하며 transports 라는 속성값으로 설정 정보를 전달할 수 있다.


const winston = require('winston');
const winstonDaily = require('winston-daily-rotate-file');
const moment = require('moment');

function tsFormat() { //로그에 타임스탬프를 찍을 수 있게 설정한다.
return moment().format('YYYY-MM-DD HH:mm:ss.SSS ZZ');
}

const logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
timestamp: tsFormat,
colorize: true,
showlevel: true,
level: 'debug',
}),
new (winstonDaily)({ //매일 새로운 파일에 로그를 기록하도록 설정

level: 'info',
filename: 'Log/logs',
timestamp: tsFormat,
datePattern: '_yyyy-MM-dd.log',
showlevel: true,
maxsize: 1000000, //로그 파일 크기가 10MB가 넘어가면 새로운 파일을 만듦
maxFiles: 5, //최대 5개까지 가능
}),

],
exceptionHandlers: [
new (winstonDaily)({
level: 'info',
filename: 'Log/exception',
timestamp: tsFormat,
datePattern: '_yyyy-MM-dd.log',
showlevel: true,
maxsize: 1000000,
maxFiles: 5,

}),
new (winston.transports.Console)({
timestamp: tsFormat,
colorize: true,
showlevel: true,
level: 'debug',
}),
],
});

logger.info('인포 로깅');
logger.error('에러 로깅');


level 설정은 어떤 정보까지 출력할 것인지 구분지으며 Logging Levels 로 구분된다. 하위 레벨은 상위레벨의 정보를 모두 포함한다.

 emerg

0

 alert

 crit

 error

 warning

 notice

 info

 debug


속성에 대한 상세한 내용은 여기를 참고하자.


'공부 > Node.js' 카테고리의 다른 글

쿠키와 세션  (1) 2019.04.02
express  (0) 2019.03.08
템플릿 엔진 모듈 - ejs, pug 모듈  (0) 2019.03.07
크롤링 - cheerio, iconv-lite 모듈  (1) 2019.03.07
npm  (0) 2019.03.06

+ Recent posts