2021-12-14 BE

블로그 내 코드 자료 출처
Node.js 교과서 개정2판(지음이 조현영)

복습

노드기능

이벤트

myEvent.onmyEvent.addListener는 동일하게 이벤트 리스너 등록 키워드이다.

const EventEmitter = require('events')
const myEvent = new EventEmitter()

myEvent.on('event1',() => {
  console.log('이벤트1 발생!')
})

myEvent.on('event1', () => {
  console.log('이벤트 1 추가 발생')
})

myEvent.once('event2', () => {
  //이벤트를 한번만 발생하게 하는 .once 키워드
  console.log('이벤트 2 발생')
})

myEvent.on('event3', () => {
  console.log('event3 발생!!!')
})

myEvent.on('event3', () => {
  console.log('event3 추가 발생!!!')
})

const listner = ('event4',() => {
  console.log('event4 발!생!')
})
myEvent.on('event4',listner)

myEvent.emit('event1') //이벤트 발생기
myEvent.emit('event2')
myEvent.emit('event2')
myEvent.emit('event3')
myEvent.emit('event4')

myEvent.removeListener('event4',listner) //내가 추했던 listner를 삭제 가능하다.
myEvent.emit('event4')

myEvent.removeAllListeners('event3') // event3에 대한 모든 listner가 삭제된다.
myEvent.emit('event3')

// myEvent.addListener myEvent.on과 동일하다.

아래와 같이 함수 형태로 만들어주면 listner명 수정에 따라 더욱 편하게 사용할 수 있다.

const listner = ('event4',() => {
  console.log('event4 발!생!')
})
myEvent.on('event4',listner)

내가 추가했던 listner를 삭제 가능하다.
아래 줄의 emit은 반응 안한다.

myEvent.removeListener('event4',listner) //내가 추가했던 listner를 삭제 가능하다.
myEvent.emit('event4')

.removeAllListeners로 모든 event listner를 삭제할 수 있다.

myEvent.removeAllListeners('event3') // event3에 대한 모든 listner가 삭제된다.
myEvent.emit('event3')

listner를 함수화 하여 만듬.
const listner = () => {
console.log('event4 발!생!')
}
myEvent.on('event4',listner)

익명 함수로(콜백형식) 만듬.
myEvent.on('event3', () => {
console.log('event3 추가 발생!!!')
})

아래 listnerCount()를 사용하여 연결된 이벤트 갯수 파악이 가능하다.

console.log(myEvent.listenerCount('event3'))

예외처리

강제적으로 서버 error를 발생 시켰다.

let i = 0
console.log('시작')

setInterval(() => {
  if(i===5) {
    throw new Error('서버를 고장내주마!')
  } else {
    console.log(i)
    i++
  }
}, 1000);

try, catch를 사용한 예외처리

예외처리를 위해 try catch 구문을 사용한다.
내가 error가 예상되는 부분을 try로 감싼다. 사진과 같이 서버가 꺼지지 않고 계속 돌아감을 알 수 있다.

let i = 0
console.log('시작')

setInterval(() => {
  try {
    if(i===5) {
      throw new Error('서버를 고장내주마!')
    } else {
      console.log(i)
      i++
    }
  } catch(err) {
    console.error(err)
  }
}, 1000);

내부 모듈 기능을 사용한 예외처리

// let i = 0
// console.log('시작')

// setInterval(() => {
//   try {
//     if(i===5) {
//       throw new Error('서버를 고장내주마!')
//     } else {
//       console.log(i)
//       i++
//     }
//   } catch(err) {
//     console.error(err)
//   }
// }, 1000);

const fs = require('fs')

setInterval(() => {
  fs.unlink('./abc.js', (err) => {
    //두 번째 인자에 콜백으로 err처리를 하면 예외처리를 지원하는 함수도 있다.
    //내부 처리임
    //이런 기능을 모르면 try,catch를 사용해도 괜찮음
    if(err) {
      console.log(err)![](https://media.vlpt.us/images/ansunny1170/post/2e01ffdf-845c-4105-932e-68dbfd99a919/image.png)
    }
  })
},1000)

내부모듈 + promises를 사용한 예외처리

const fs = require('fs').promises

setInterval(() => {
  fs.unlink('./abc.js').catch(err => {
    console.error(err)
  })
}, 1000);

에러 발생지를 모를 때 예외처리

권장하는 방법은 아니나 에러 위치 파악이 어려울 때 사용한다.
최후의 수단으로 사용하고, 안쓸수록 좋다.

process.on('uncaughtException',(err) => {
  console.error('예기치 못한 에러', err)
})

setInterval(() => {
  throw new Error('서버를 고장 낼꺼야')
}, 1000);
setInterval(() => {
  console.log('정상진행')
}, 1000);

자주발생하는 error들 p.165

공부

http 모듈로 서버 만들기

요청과 응답 이해하기

http.createServer(res,res)

http.createServer((request, response) =>{ })서버를 만들기 위한 메소드

const http = require('http')

http.createServer((request,response) => {
  response.writeHead(200,{'Content-Type' : 'text/html;charset=utf-8'})
  //객체 형태로 기입하고 내용은 html형태로 받겠다라는 뜻이다. 그리고 utf-8형식으로 받겠다.
  response.write('<h1> hello node! </h1>')
  response.end('<p> hello server! </p>')
  //end를 꼭 써야 응답을 끝낼 수 있다. 끝내면서 content를 또 전달 할 수 있다.
})

.listen(3000, () => {
  console.log('포트 3,000번으로 실행하고 있음!')
})

다른사람의 IP를 통하여 접근도 가능하다.

cmd에서 ipconfig 입력하여 IPv4 에서 WIFI주소 확인 가능하다.

http.createServer 실습

이벤트 리스너 방식

const http = require('http')

const server = http.createServer((request,response) => {
  response.writeHead(200,{'Content-Type' : 'text/html;charset=utf-8'})
  response.write('<h1> hello node! </h1>')
  response.end('<p> hello server! 김예찬! </p>')
})

server.listen(3000)
server.on('listening',() => {
  console.log('이벤트 리스너 방식으로 포트 3000번 포트에서 실행하고 있음')
})
server.on('error',(error) => {
  console.error(error)
})

여러개 서버 한번에 열기

const http = require('http')

const server = http.createServer((request,response) => {
  response.writeHead(200,{'Content-Type' : 'text/html;charset=utf-8'})
  response.write('<h1> hello node! </h1>')
  response.end('<p> hello server! 김예찬! </p>')
})

server.listen(3000)
server.on('listening',() => {
  console.log('이벤트 리스너 방식으로 포트 3000번 포트에서 실행하고 있음')
})
server.on('error',(error) => {
  console.error(error)
})
//server1 = 두 번째 서버
const server1 = http.createServer((request,response) => {
  response.writeHead(200,{'Content-Type' : 'text/html;charset=utf-8'})
  response.write('<h1> hello node1! </h1>')
  response.end('<p> hello server1! 김예찬! </p>')
})

server1.listen(3001)
server1.on('listening',() => {
  console.log('이벤트 리스너 방식으로 포트 3001번 포트에서 실행하고 있음')
})
server1.on('error',(error) => {
  console.error(error)
})

html파일을 읽어서 createServer()하기 async-await

<html>
  <head>
    <meta charset="utf-8">
    <title>Nodejs 웹서버</title>
  </head>
  <body>
    <h1> 만들준비 되었나?</h1>
    <p> server 만들어 보자</p>
  </body>
  </html>
//async-await 방식
const http = require('http')
const fs = require('fs').promises

const server = http.createServer(async (req,res) => {
  try{
    const data = await fs.readFile('./index.html')
    res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'})
    res.end(data)
  } catch (err) {
    console.error(err)
  }
})

server.listen(3000)

server.on('listening', () => {
  console.log('서버 3000번으로 실행하고 있음')
})


html파일을 읽어서 createServer()하기 .then

//.then 방식
const http = require('http')
const fs = require('fs').promises

const server = http.createServer((req,res) => {
  const html = fs.readFile('./index.html')
  html.then((data) => {
    res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'})
    res.end(data)
  }).catch((err) => {
    console.error(err)
  })
})

server.listen(3001)

server.on('listening', () => {
  console.log('서버 3001번으로 실행하고 있음')
})

서버 오류발생 시, 서버 죽지 않고 클라이언트에게 전달

이렇게 에러메시지 띄우는 것을, 예외처리 혹은 error handling이라고도 한다.

//async-await 방식
const http = require('http')
const fs = require('fs').promises

const server = http.createServer(async (req,res) => {
  try{
    throw new Error('에러메시지 : 표시할 수 없는 화면입니다.') // 에러를 강제로 내보자
    //에러를 강제로 내면 catch에 잡힌다. 위에 에러메시지가  catch(err)의 err로 전달이 된다.
    const data = await fs.readFile('./index.html')
    res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'})
    res.end(data)
  } catch (err) {
    console.error(err)
    res.writeHead(500,{'Content-Type':'text/html; charset=utf-8'})
    res.end(`<p>${err.message}</p>`)
    //500 : 앞 인자는 응답코드다. 500번대는 서버 오류.
    //오류 발생 시, 사용자에게 오류상황을 알려줘야한다.
    //그럼 강제적으로 오류 발생을 어떻게 시킬까? 맨윗출 throw new Error이다.
  }
})

server.listen(3000)

server.on('listening', () => {
  console.log('서버 3000번으로 실행하고 있음')
})

REST란?

Representational State Transfer 의 약자

  • 요청 메소드 : GET, POST, PUT, PATCH, DELETE, OPTIONS
  • GET : 서버 자원을 가져오고자 할 때 사용. 요청의 본문에 데이터를 넣지 않는다. 데이터를 서버로 보내야 한다면 쿼리스트링을 사용한다.
  • POST : 서버에 자원을 새로 등록하고자 할 때 사용. 요청의 본문에 새로 등록할 데이터를 넣어 보낸다.
  • PUT : 서버의 자원을 요청에 들어 있는 자원으로 치환하고자 할 때 사용. 요청의 본문에 치환할 데이터를 넣어 보낸다.(전체다 변경해야 해서 불편할 때가 있다.)
  • PATCH : 서버 자원의 일부만 수정하고자 할 때 사용. 요청의 본문에 일부 수정할 데이터를 넣어 보낸다.
  • DELETE : 서버의 자원을 삭제하고자 할 때 사용. 요청의 본문에 데이터를 넣지 않는다.
  • OPTIONS : 요청을 하기 전에 통신 옵션을 설명하기 위해 사용. 12장에 자주 나온다.

restServer.js 핵심 코드

코드로 보면 req.method로 HTTP 요청 메서드를 구분하고 있다.
메서드가 GET이면 다시 req.url로 요청 주소를 구분한다.
주소가 /일 때는 restFront.html을 제공하고,
주소가 /about이면 about.html파일을 제공한다.

이외의 경우에는 주소에 적힌 파일을 제공한다.
/restFront.js라면 restFront.js파일을 제공할 것이고,
/restFront.css라면 restFront.css파일을 제공할 것이다.

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

const users = {}; // 데이터 저장용 db를 안쓰니 임시용으로 사용

http.createServer(async (req, res) => {
  try {
    console.log(req.method, req.url);
    if (req.method === 'GET') { //GET요청이 왔을 때, 아래 3가지 요청이 있는 것이다.
      if (req.url === '/') { // '/'는 라우터라고 하며,
        const data = await fs.readFile('./restFront.html');
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data); //.end에 return을 붙여준 이유 p.184
      } else if (req.url === '/about') {
        const data = await fs.readFile('./about.html'); //about.html을 보여줘라!
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
        return res.end(JSON.stringify(users)); //users는 현제 객체 형태이다.
        //JSON형식이니 문자형으로 바꿔줘!
      }
      // /도 /about도 /users도 아니면
      try {
        const data = await fs.readFile(`.${req.url}`);
        return res.end(data);
      } catch (err) {
        // 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
      }

다른 HTTP 요청 메서드들을 추가하고, 데이터베이스 대용으로 users라는 객체를 선언하여 사용자 정보를 저장하였다.

POST /user 요청에서는 사용자를 새로 저장하고 있으며, PUT /user/아이디 요청에서는 해당 아이디의 사용자 데이터를 수정하고 있다.

DELETE /user/아이디 요청에서는 해당 아이디의 사용자를 제거한다.

POST와 PUT 요청을 처리할 때 조금 특이한 것을 볼 수 있다.
바로 req.on('data')req.on('end')의 사용이다.
요청의 본문에 들어 있는 데이터를 꺼내기 위한 작업이라고 보면 된다.
req와 res도 내부적으로는 스트림(각각 readStream과 writeStream)으로 되어 있으므로 요청/응답의 데이터가 스트림 형식으로 전달된다.
또한 on에서 볼 수 있듯이 이벤트도 달려 있다.
다만 받은 데이터는 문자열이므로 JSON으로 만드는 JSON.parse 과정이 필요하다.

    } else if (req.method === 'POST') {
      if (req.url === '/user') {
        let body = '';
        // 요청의 body를 stream 형식으로 받음
        req.on('data', (data) => {
          body += data;
        });
        // 요청의 body를 다 받은 후 실행됨
        return req.on('end', () => {
          console.log('POST 본문(Body):', body); //우측 body에 최종적으로 들어온다.
          const { name } = JSON.parse(body); //좌측 name을 찾아서 구조분해할당을 했다.
          const id = Date.now();
          users[id] = name; //키밸류형식으로 등록하였다.
          res.writeHead(201);
          res.end('등록 성공');
        });
      }
    } else if (req.method === 'PUT') {
      if (req.url.startsWith('/user/')) {
      //start가 user/라고 되어있으니 user/1 도 속한다.
      //req.url.startsWith -> 문자열.startsWith
        const key = req.url.split('/')[2]; 
        //문자열 기능중에 split기능이 있다. 구분인자는 `/`, [2]는 인덱스 호출번호.
        // ` /user/1`을 split하면 ' ', 'user', '1' 3가지 인자가 배열에 들어간다.
        //이러한 것들을 key값에 넣어주겠다라는 뜻이다. timestamp로 들어간다.
        let body = '';
        req.on('data', (data) => {
          body += data;
        });
        return req.on('end', () => {
          console.log('PUT 본문(Body):', body); //우측 body에 데이터가 다 담겼다.
          users[key] = JSON.parse(body).name;
          //위에서 timestamp로 받은 값에 대한 users배열을 수정하게 되겠다.
          return res.end(JSON.stringify(users));
        });
      }
    } else if (req.method === 'DELETE') {
      if (req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2];
        delete users[key]; //object안의 원하는 item을 삭제하고 싶다면!!
        return res.end(JSON.stringify(users));
      }
    }
    res.writeHead(404);
    return res.end('NOT FOUND');
  } catch (err) {
    console.error(err);
    res.writeHead(500);
    res.end(err);
  }
 })
  .listen(8082, () => {
    console.log('8082번 포트에서 서버 대기 중입니다');
  });
  • 클라이언트 HTTP 요청

※ 중요! 핵심코드 실습 주석 확인 필요

const http = require('http')
const fs = require('fs').promises

const users = {} // 데이터 저장용

const server = http.createServer(async(req,res) => {
  try {
    if (req.method === 'GET') { //GET메소드로 들어 왔다면!
      if(req.url === '/'){ //url이 기본 url로 들어 왔다면! 첫 화면을 뜻함
        const data = await fs.readFile('./restFront.html') //그러면 해당 html읽어서 보여줄께! 
        res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'})// writeHead가 없다면 위 html파일이 평문으로 들어간다!
        return res.end(data)
      } else if (req.url === '/about') {
        const data = await fs. readFile('./about.html') //about 버튼을 눌러보자
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
        return res.end(data)  
      } else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' })
        return res.end(JSON.stringify(users)) //JSON으로 바꿔서 보내준다
      } 
      try { // ./restFront.css, restFront.js파일을 불러오면
        const data = await fs.readFile(`./${req.url}`) //메서드가 GET이면 다시 req.url로 요청 주소를 구분한다! //.then의파라미터에 해당하는 것이 await...이다. data가 곧 .then의 파라미터
        return res.end(data)
      } catch(err) {
        console.error(err)
      }
    } else if (req.method === 'POST') {
      if(req.url === '/user') {
        let body = '' //빈문자열을 만든다. string으로 받으려 한다. 무엇을?

        req.on('data',(data) => {
          body = body + data 
        })

        return req.on('end',()=> {
          console.log('POST 본문 (body) : ', body)
          const {name} = JSON.parse(body) //body에 담긴 부분이 JSON이고 parse(파싱)을 해줘서 object로 만들어서 넣는다. 객체화 한다.
          //JSON은 객체는 아니지만 형태는 객체처럼 문자열로 온다.
          const id = Date.now() //현제시간 timestamp 형태로 찍힌다.
          users[id] = name//프론트에서 name값을 받아서 백엔드로 준다. 그걸 가지고 users객체에 담을 것이다. 그때 키가 필요하고, 키는 현재시간과 
          //처음 user={}빈객체이고 위의 id가 123으로 들어왔고, name이 김예찬으로 들어왔따면 {123:김예찬}이렇게 저장된다.
          //users[id] id는 ''스트링 형식으로 들어간다.
          console.log(users)
          res.writeHead(201,{ 'Content-Type': 'text/plain; charset=utf-8' })
          res.end('ok')
        })
      }
    } else if (req.method === 'PUT') {
      if (req.url.startsWith('/user/')) { // /user/1333.adsdf 이렇게 하면 다된다. /user/로 시작만 하면
        // 주소 : 3000/user/123124123 이런식으로 요청이 들어온다. 그래서 split으로 처리한다.
        console.log(req.url) //rul이 어떻게 들어오는지 알 수 있다. 
        const key = req.url.split('/')[2] //'/' 기준으로 잘라준다. /user/123 => ['','user','123'] 문자열을 배열화 한다.
        console.log(key)
        let body =''
        req.on('data', (data) =>{ //data가 chunk로 온다?!
          body +=data
        })
        return req.on('end', ()=> {
          console.log('PUT 본문 : ',body)
          users[key] = JSON.parse(body).name
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8'})
          res.end('ok')
          // return res.end(JSON.stringify(users));
        })
      }
    } else if (req.method === 'DELETE') {
      if(req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2]
        delete users[key]
        res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'})
        res.end('ok')
      }
    }

  } catch (err) { //err은 변수 이름으로 error해도 된다.
    console.error(err)
    res.writeHead(500,{'Content-Type': 'text/plain; charset=utf-8'})
    res.end(err.message)
  }
})

server.listen(3000)

server.on('listening', () => {
  console.log('포트 3000번 사용중')
})
server.on('error', (err) => { //err은 변수 이름으로 error해도 된다.
  console.error(err)
})

쿠키와 세션

쿠키 설정해 보기 자습 p.190

  • 로그인 할 때 사용, 세션유지
    로그인 하면, 로그인 성공했어라고 하고 서버에서 쿠키하나 줄께.... 하는듯이 ㅋㅋ
    로그인한 상태를 유지시켜준다. 그리고 만료시간도 있다.

일반적으로 Head에 담아보낸다.

const http = require('http');

http.createServer((req, res) => {
  console.log(req.url, req.headers.cookie);
  res.writeHead(200, { 'Set-Cookie': 'mycookie=test' }); //여기에 쿠키 있음.
  res.end('Hello Cookie');
})
  .listen(8083, () => {
    console.log('8083번 포트에서 서버 대기 중입니다!');
});

좋은 웹페이지 즐겨찾기