Node.js Web Server 기초

0

Node

목록 보기
1/2
post-thumbnail

혹시나 잘못된 개념 전달이 있다면 댓글 부탁드립니다. 저의 성장의 도움이 됩니다

오늘의 Checkpoint

SOP

동일 출처 정책 Same-Origin Policy
-> 출처 Origin가 같은 리소스만 공유 가능
-> 보안을 위한 기능 : 기본적인 기능

  • Origin = Protocla(scheme) + Hosts(+port)

    ex. https://www.google.co.kr/, https://www.google.co.kr:443/ 은 동일 출처이므로 리소스 공유 가능

⭐️ CORS

교차 출처 리소스 공유 Cross-Origin Resource Sharing
Origin이 다른 경우에도 리소스를 공유할 수 있도록 권한을 부여하는 체제
-> 서버의 응답 헤더(HTTP 헤더)에 'Access-Control-Allow-Origin' 추가하여 CORS 설정


CORS 동작 방식 3가지

Preflight Request

요청을 보내기 전에 OPTIONS 메서드로 리소스 접근이 가능한지 확인
항상 메인 요청 전에 프리플라이트 요청 전달

  1. 프리플라이트 요청 전달
    : 클라이언트 -> 브라우저 -> 서버

  2. Access-Control-Allow-Origin 응답
    : 서버 -> 브라우저
    모든 HTTP 응답 헤더에 포함
    cf. 없으면 CORS 에러 + 실제 요청 전달 X

  3. 실제 요청
    : 브라우저 -> 서버

  4. 요청에 대한 응답
    : 서버 -> 브라우저 -> 클라이언트


Prefilght 장점

  • 효율성
    : 권한 확인 되는 경우만 요청 허용

  • 리소스 보호
    : 요청이 바로 들어오면 권한이 없는 경우에도 서버측에서는 DELETE, PUT 등의 요청이 수행되어 리소스의 변화가 생길 수 있음(권한이 없으면 클라이언트는 응답 수신 불가)

Simple Request

여러 조건이 충족될 경우 프리플라이트 과정을 생략하고 요청을 보냄

  • GET, HEAD, POST 요청일 경우
  • 헤더에 포함된 내용에 대한 조건 충족
    • 자동설정 + 4가지 헤더만 추가 가능
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type 의 값은 3가지
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain

Credentialed Request

헤더에 인증 정보를 포함한 요청일 경우
-> 프론트, 서버 양쪽에서 CORS 설정 필수

  • 프론트의 요청 헤더 : withCredentials : true
  • 서버 요청 헤더
    : Access-Control-Allow-Credentials : true O
    Access-Control-Allow-Credentials : * X (모든출처 허용으로 설정 X)

CORS 설정

헤더의 값을 변경하는 방식으로 CORS 권한 설정

Node.js 서버로 CORS 적용

const http = require('http');

const server = http.createServer((request, response) => {

  response.setHeader("Access-Control-Allow-Origin", "*"); // 모든 도메인
  response.setHeader("Access-Control-Allow-Origin", "https://codestates.com"); // 특정 도메인
  response.setHeader("Access-Control-Allow-Credentials", "true"); // 인증 정보를 포함한 요청
})

Express(프레임워크) 서버로 CORS 적용

const cors = require("cors");
const app = express();

//모든 도메인
app.use(cors());

//특정 도메인
const options = {
  origin: "https://codestates.com", // 접근 권한을 부여하는 도메인
  credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
  optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};

app.use(cors(options));

//특정 요청
app.get("/example/:id", cors(), function (req, res, next) {
  res.json({ msg: "example" });
});

Node Server

Node.js는 http 모듈을 사용하여 웹 서버를 구축

  • request 객체
  • response 객체

HTTP 트랜잭션 해부(Anatomy of an HTTP Transaction) 참조
Node.js HTTP 모듈 참조
cf. postman으로 테스팅

웹서버 실행
: HTTP 요청을 보내고 응답을 받을 수 있는 프로그램
-> Node.js도 HTTP 모듈을 사용하여 웹 서버 구축

cf. 서버 코드를 수정하면 저장 후 터미널에서 node <파일명> 재실행 시켜야함
nodemon 개발 도구를 설치하면 자동 새로고침 가능
npm install nodemon 설치 -> npx nodemon server/basic-server.js 실행
( package.json에 코드를 저장할 때는 npx 없이 "start": "nodemon server/basic-server.js" )

클라이언트 실행
특정포트로 클라이언트를 실행하려면 터미널에 npx serve -l 포트번호 client/ 입력
serve 참조

서버 생성

  • HTTP 모듈 호출
  • createServer 메서드 내부에서 요청/응답에 대한 로직 서술
  • listen 메서드로 생성한 서버 호출하야 통신 가능
const http = require("http");
const port = '4021'; // 포트번호 안겹치게 설정

const server = http.createServer((request, response)=>{
	// 이 부분에서 요청을 받고, 응답을 하는 로직 추가 작업 진행
})

server.listen(port, ()=>{
	console.log('listen 까지 사용해야 서버 열림');
})

요청

클라이언트로 전달받은 request는 객체로 수신
-> 요청 HTTP 메서드는 request.method, 엔드포인트는 request.url, request.headers 처럼 받아서 사용

구조분해 할당을 활용하여 사용

const { method, url } = request;

요청의 바디

console.log(req.body); // undefined

HTTP 요청 메세지의 바디(payload)는 Buffer 조각들(chunk)로 나뉘어서 서버에 전달되는데, 이 조각들을 하나로 합쳐주는 과정을 거친 후 사용이 가능
cf. express 프레임워크를 사용하면 이 과정이 생략

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // 요청 바디를 문자열로 변환함
  // 비동기 함수이므로 'end' 이벤트발생할 때 수행되는 함수의 내부에서 응답하는 로직 추가
});

data, end 이벤트에 이벤트리스너 등록

request.on('data', (chunk) => {/* request의 바디가 있을 때 수행 */})
 	   .on('end', () => {/* 전달되는 데이터가 없을때 수행 */});

// 버튼에 이벤트리스너 등록과 비교
btn.addEventListener('click',()=>{/* 생략 */});

응답 상태, 상태메세지

response 객체로 응답
-> 응답이 없으면 로딩 상태 유지

// 암묵적 헤더 설정
response.statusCode = 200;
response.setHeader('Content-Type', 'application/json');

// 명시적 헤더 설정
response.writeHead(200, {'Content-Type': 'application/json'});

응답의 바디

response.write('<h1>Hello, World!</h1>');
response.end();

// 통합 표현
response.end('<h1>Hello, World!</h1>');

에러 처리

error 이벤트가 발생할 때 수행

request.on('error', (err) => {
  // error 이벤트 발생하면 아래의 동작 수행
  console.error(err.stack);
});

응답과 라우팅 + CORS 설정

  • CORS 설정
    : OPTIONS 메서드로 요청이 들어올 경우, Access-Control-Allow-Origin를 헤더에 추가
    ( 다른 HTTP 메서드에서도 CORS 설정 필요 )
  • 라우팅
    : Node.js의 라우팅은 메서드와 Endpoint를 조건문으로 분기하여 처리

const http = require('http');

const server = http.createServer((request, response) => {
  let body = [];
  request.on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    
    // 프리플라이트 -> CORS 설정
    if(request.method === 'OPTIONS'){
      response.writeHead(200, defaultCorsHeader);
      response.end();
    }
    // POST /lower 요청
    else if(request.method === 'POST' && request.url ==='/lower'){
      response.writeHead(201, defaultCorsHeader); // CORS를 위한 부분
      response.end(body.toLowerCase());
    }
    // POST /upper 요청
    else if(request.method==='POST' && request.url ==='/upper'){
      response.writeHead(201, defaultCorsHeader); // CORS를 위한 부분
      response.end(body.toUpperCase());
    }
    // 에러 처리
    else {
      response.writeHead(404, defaultCorsHeader); // CORS를 위한 부분
      response.end('Bad Request')
    }
   
  });

  // 에러 발생할 때 
  request.on('error',(err)=>{
    console.error(err);
  });
});

server.listen(4998);

// CORS를 위해 Access-Control-Allow-Origin 헤더에 추가
const defaultCorsHeader = {
  'Access-Control-Allow-Origin': 'http://localhost:3000',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

참고

인스펙터를 활용하여 브라우저에서 콘솔 확인 가능
npx nodemon --inspect <파일이름>






오늘의 나

느낀점
오늘 조금 가랑이 찢어진다는 느낌이었다. 페어랑 수준차이가 많이 나서 그런것 같았다. 공식문서도 제대로 못본 상태에서 페어분 얘기 듣고 흐름대로 코드 작성하는데 버거웠다. 공식문서에서 방법에서 필요없을 것 같은 부분을 빼고 진행되었는데 나중에 공식문서랑 비교해보니 너무 달라서 비교볼수록 혼란스러운 것 같다. 완전히 흡수 못했던 상태로 생략을 하니까 소화가 안된 것 같다.
그리고 제공된 설명은 거의 없지만 공식문서를 보고 진행을 해야해서 시간이 너무 부족했다. 그래서 커리큘럼 내용만 보고 진행했더니 버거웠다. 짧은 시간 내에 글로 읽어서 이해하는게 나에겐 잘 안맞는 것 같다. 빨리 읽어야한다는 생각때문인지 머리 속에 안남고 파편이 들어온 것처럼 흩뿌려진 상태였다. 이번 주말도 빡공인가보다.

개선점 및 리마인드

for문 대신 고차함수를 고집하지는 말자!

매일 코딩문제를 풀면서 for문 대신 고차함수가 더 좋다는 생각으로 오늘 for문으로 사용했으면 불필요한 과정을 쓰지 않아도 되었을 텐데, 고차함수를 고집하니까 중간에 오류도 발생하고 불필요한 부분을 구현했다.
고차함수를 적용하기 위해
1. 문자열을 배열로 변경
2. 배열을 객체로 변경
3. 객체를 반복문으로 체크
이 세가지가 for문 대신 forEach()를 사용하기 위해 불필요하게 거친 작업이었다.
게다가 2번 과정에서 '반복되는 문자가 있는 경우' 반복문을 다 돌필요없이 false를 리턴하면 된다고 해서 리턴을 적용했고 한참동안 왜 함수의 결과가 true로 나왔는지 들여다봤다. 콜백함수의 리턴은 외부함수의 리턴이 아니었음을 다시 상기시킬 수 있었다.

//문자열 내부에 문자 중복이 있는지 확인
// 고차함수 사용할 때 함수 내부 코드
/*
  문자열 내부에 문자 중복이 있는지 확인
  대소문자 구분 없음 -> 소문자로 변경 후 테스트 진행
  
  배열로 변경 후, 객체화
  키가 있으면 false 리턴
*/

  const target = str.toLowerCase();
  
  // 객체화 작업
  const arr = target.split('');
  const obj = {};
  arr.forEach((el)=>{
    if(obj[el]){
      obj[el]++;
      // return false; <--- arr.forEach의 반환값이므로 isIsogram의 반환값 아님
    } else {
      obj[el] = 1;
    }
  })
  
  // value값이 1이 아니면 false 리턴
  for(let keys in obj){
    if(obj[keys] !== 1){
      return false;
    }
  }
  return true;
}
// for문 사용했을떼 함수 내부 코드
  const target = str.toLowerCase();
  const obj = {};

  for(let i = 0; i<target.length; i++){
    if(obj[target[i]]){
      // obj[target[i]]++;
      return false;
    } else {
      obj[target[i]] = 1;
    }
  }
  return true;

0개의 댓글