2-10. WEB server

해피데빙·2022년 2월 9일
0

TIL

목록 보기
13/45

백엔드

  • node.js를 이용
  • api server 구현
  • express, 라우팅, server-side 디버깅하는 방법 학습

브라우저와의 차이

  • node.js 환경에서 commonJS, 모듈 등 브라우저에서 찾아볼 수 없는 다소 생소한 개념들 포함
  • 백엔드뿐만 아니라 프론트엔드에서도 적극 사용한다

api 서버는 프론트엔드라도 구현할 수 있어야 한다
필요한 데이터를 저장하거나 불러오는 기본적인 API 서버를 구현할 수 없다면
아무런 동적 활동을 할 수 없는 클라이언트만 구현하게 된다
node.js에 대한 내용은 프론트엔드와 백엔드 등 웹 개발에서 공통적으로 사용

node.js의 역할
: js가 돌아가는 환경(런타임)

package.json의 역할
: dev-dependencies 등 install한 package의 버전 관리 가능

http의 역할 및 특징, methods(verbs)의 종류와 각각의 목적
: GET, POST, PUT, PATCH, OPTIONS
API의 개념, API 문서를 읽는 방법
: interface (클라이언트, 서버 간의 메뉴판 : 클라이언트가 필요한 정보를 얻기 위해 보는 서버의 메뉴판)

HTTP

  • 요청/응답을 브라우저를 통해 확인, 해당 내용을 읽을 수 있다
  • 다양한 요청 방식과 응답 코드에 대해 이해할 수 있다

request -> response
GET 등

node.js modules 사용
내장 http 모듈 사용 가능
const http = require('http')

http 모듈 사용 시에 서버에 CORS 설정을 할 수 있다
ALLOW~~:

CommonJS를 이용한 모듈 내보내기/불러오기 가능
??

라우팅과 API

  • 라우팅(조건에 다른 분기)를 이해하고 이를 서버 코드에서 구현할 수 있다
if(request.method === 'POST' && request.url === '/upper'){} 
if(request.method === 'POST' && request.url === '/lower'){} 
if(request.method === 'OPTIONS'){} 

=> 요청, url 등에 따른 분기

  • 클라이언트가 사용할 수 있도록, 서버 API 문서를 직접 작성할 수 있다
    ??
    ex. fetch할 때 path 등

Express 라이브러리
-어떤 작업을 단순하게 만드는지 이해
-미들웨어의 개념 이해

서버 개발과 디버깅
-CRUD를 수행하는 웹 서버 개발 방법을 익힌다
-서버 개발을 돕는 다양한 툴들을 익힐 수 있다

CORS

브라우저에서 크로스 도메인 요청은 기본적으로 제한되어 잇다
웹 앱 고도화를 위해 개선 요청

모든 도메인을 허용한다
메소드는 GET, POST, PUT, DELETE, OPTIONS만 허용
헤더에는 content-type, accept만 쓸 수 있다
preflight request는 10초까지만 허용

서버에서 allow하는 조건들을 다 맞추고 있는가?
사전에 서버에 확인하는 요청

서버에서 access-control-allow-origin: codestates.com
그럼 codestates.com이라는 도메인에서 리소스 요청하는 것에 대해 허용을 해준다

Sprint: MINI NODE SERVER

클라이언트의 액션(버튼 클릭)에 따라 각기 다른 HTTP 요청을 서버로 보내고,
HTTP 요청에 담아 보낸 단어를 소문자 또는 대문자로 변경된 단어를 응답으로 받아 화면에 보여 줍니다.

Endpoint(URL) Method 기능
/lower POST 문자열을 소문자로 만들어 응답해야 합니다
/upper POST 문자열을 대문자로 만들어 응답해야 합니다

cf.
CORS 관련 헤더를 OPTIONS 응답에 적용해야 합니다.
: 클라이언트의 preflight request에 대한 응답을 돌려줘야 합니다.
: preflight request에 대한 응답 헤더는 이미 작성되어 있습니다

preflight request 다시 보기 (언제 일어나는지 : 오리진 차이?? 어떤 조건들??)

Client

index.html

input-text : 요청 받는 box
response-wrapper: 응답 받는 box

app.js

post 함수
: fetch(url,{
method : 'POST',
body: JSON.stringify(body)
headers: {
'Content-Type': 'application/json'
}
})

toLowerCase, toUpperCase 버튼을 클릭하면 post 함수실행
path에 lower, upper
body에 input-text에 넣은 값을 넣어서 request.body로 보낸다
.then으로 response를 받은 다음 1)res.json() 2)render(res)

render함수에서는 해당 데이터를 resultWrapper 안에 넣어준다

클라이언트 설명

  1. 브라우저에는 서버에 요청을 보내기 위해 fetch 같은 HTTP 요청을 보내는 도구가 기본 내장
  2. 클라이언트에서 서버에 fetch로 ajax요청을 보낸다
    cf. ajax요청
  • js, DOM : 부분적으로 바꿔준다
    • 응답 아래 박스에 응답을 받는다 (부분적 렌더링)
  • fetch : 비동기, promise를 사용할 수 있게 한다
    • 서버에 요청을 보내고 .then으로 응답을 받을 때까지 기다렸다가 사용한다
    • url, request 객체(method, body, headers)
  1. access control scenario( simple / preflight )
    simple request의 조건들
  • get, head, post 중 하나면 preflight request 가 트리거되지 않는다
  • user agent가 자동으로 세팅된 헤더로만 되어야 한다
  • content-type는 아래 중 하나여야 한다

이 예시는

  • header의 content-type가 application/json이므로 post요청이지만 simple request가 아니다
    그러므로 options method로 preflight request가 온다?? 물어보기

아무튼 preflight request가 온다

SERVER

basic-server.js



0. 서버는 클라이언트(브라우저)의 HTTP 요청에 알맞은 응답을 보낼 수 있도록 조작 필요
->node.js는 HTTP 요청을 보내거나 응답을 받을 수 있는 도구를 제공한다
웹 서버 : HTTP 요청을 처리하고 응답을 보내주는 프로그램

node.js의 http 모듈 이용
-> http 요청을 처리하고 응답을 보내준다
cf. node.js에서 파일을 읽거나 쓰기 위해 fs모듈 쓰듯이 http 요청과 응답을 다루기 위해 http 모듈 사용

HTTP 트랜잭션 해부 (http 모듈 api 문서)
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction/
직접 작성해보면서 키워드를 검색하면서 이해한다
한번에 다 이해하기에는 어려울 수 있다

서버를 키는 터미널 명령어 : node server/basic-server.js(서버 파일 이름)

  1. preflight request
    클라이언트인 브라우저랑 서버인 basic-server.js이 다른 오리진을 사용한다
    ( localhost:5500/파일이름/index.html !== localhost:4999 )

cf. 재밌는 사실
브라우저와 서버 둘다 localhost로 열었지만
client는 index.html이 있어서 localhost:5500로 파일 찾아 들어가면 화면이 나오고
server는 js파일만 있어서 http://localhost:4999로 들어가면 root directory에 get 요청만 한 것으로 들어간다

아무튼 클라이언트와 서버가 다른 오리진을 사용하기 때문에 CORS가 일어난다
=> CORS: 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

preflight request도 일어나는데 (왜??)
OPTIONS라는 method를 사용해 http 요청을 한다
이때 cors를 조작하기 위해 아래 header를 추가해야 한다

const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

//위와 같은 종류의 메소드, 헤더, 오리진을 허용한다는 의미

header를 추가하는 방법
:

http모듈에 있는 방법
1)server 객체를 http.createServer(콜백함수)로 만든다
Server 객체는 EventEmitter(??)이고 여기서는 server 객체를 생성하고 리스너를 추가하는 축약 문법을 사용한 것입니다

원래 형태

const server = http.createServer();
server.on('request', (request, response) => {
  // 여기서 작업이 진행됩니다!
});

//아래서 on, 'request'라는 첫 인자 없이 바로 사용

2)콜백함수에서 파라미터로 request, response를 받는다

HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며
요청 핸들러 함수를 호출한다

요청을 실제로 처리하려면 listen 메서드가 server 객체에서 호출되어야 한다
=> 대부분은 서버가 사용하고자 하는 포트 번호를 listen에 전달하기만 하면 됩니다. 몇 가지 다른 옵션도 있으므로 API 문서를 참고하세요.

이렇게 createServer를 하고 이후에 listen(포트번호)을 사용하는 것

(1) request
: request에 클라이언트에서 보낸 요청의 request객체가 들어온다
: request 안에 method, url, headers 등의 키 존재

cf. url은 전체 URL에서 서버, 프로토콜, 포트를 제외한 것으로, 세 번째 슬래시 이후의 나머지 전부라고 볼 수 있습니다.
cf. headers에 user-agent 등의 헤더들이 들어가 있다 (모두 소문자로 받는다) 

1-1. 여러 요청 시나리오에 따라 라우팅해서 다른 응답을 보낸다 
const server = http.createServer((request, response) => { 
 
 const {method, url} = request; 
 if(method === 'OPTIONS'){ 
 	
 }
 
  if(method === 'GET' && url==='/upper'){
  
 }
 
  if(method === 'POST' && url === '/lower'){
  
  }
  
  
  }

1-2. 요청 바디 조작해서 response로 보내기
POST, PUT 요청을 받을 때 애플리케이션에 요청 바디는 중요하다
request객체는 readableStream 인터페이스 구현(??)
이때 이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있다
스트림의 data, end 이벤트에 이벤트 리스너 등록해서 데이터 받을 수 있다

data 이벤트에서 발생시킨 청크는 Buffer
이 청크가 문자열 데이터라는 것을 알고 있다면(케바케)
이 데이터를 배열에 수집한 다음 end 이벤트에 이어붙인 다음 문자열로 만든다

=> 다행히 npm에 concat-stream나 body 같은 모듈로 대체 가능(for later though)

-> 이 예제에서는 input 박스에 넣은 값을(post 요청) upperCase해서 돌려보낸다(response)

 let body = [];

   request.on('data', (chunk) => {
    body.push(chunk);
    }).on('end', () => {
    body = Buffer.concat(body).toString().toUpperCase();


    //Buffer??
    // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
    response.end(body)
    //end에 body자체 넣은 거 

    });

request.on(이벤트, 이벤트 리스너(핸들러? 둘의 차이가 뭐지))
(1) 첫번째 이벤트 : data
-> data가 있으면 body라는 배열에 chunk 형태로 잘라 넣는다 (아마)
(2) 두번째 이벤트 : end
-> end가 일어나면 Buffer(?)에 body라는 배열을 concat하고 전체 string으로 만든 다음 toUpperCase를 한다

response.end(body)
uppercase한 string이 클라이언트에 응답으로 전송되고 응답이 끝난다

(2)response

2-1. HTTP 상태 코드
따로 설정하지 않으면 응답의 HTTP 상태 코드는 항상 200입니다.
물론 모든 HTTP 응답이 이를 보장하는 것은 아니고 어떤 경우에는 다른 상태 코드를 보내기를 원할 것입니다. 상태 코드를 변경하려면 statusCode 프로퍼티를 설정해야 합니다.

response.statusCode = 404;

// 클라이언트에게 리소스를 찾을 수 없다고 알려줍니다.

2-2. 응답 헤더 설정
편리한 setHeader 메서드로 헤더를 설정합니다.

response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');

응답에 헤더를 설정할 때 헤더 이름의 대소문자는 중요하지 않습니다.
헤더를 여러 번 설정한다면 마지막에 설정한 값을 보낼 것입니다.

2-3. 명시적인 헤더 데이터 전송
지금까지 설명한 헤더와 상태 코드를 설정하는 메서드는 "암묵적인 헤더"를 사용하고 있다고 가정합니다.
이는 바디 데이터를 보내기 전 적절한 순간에 헤더를 보내는 일을 노드에 의존하고 있다는 의미입니다.

원한다면 명시적으로 응답 스트림에 헤더를 작성할 수 있습니다. 헤더를 작성하는 writeHead 메서드가 있습니다. 이 메서드는 스트림에 상태 코드와 헤더를 작성합니다.

response.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'bacon'
});

=> 일단 헤더를 설정하고 나면 응답 데이터를 전송할 준비가 된 것입니다.

2-4.응답 바디 전송
response 객체는 WritableStream이므로 클라이언트로 보내는 응답 바디는 일반적인 스트림 메서드를 사용해서 작성합니다.

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

스트림의 end 함수에 스트림에 보낼 데이터의 마지막 비트를 선택적으로 전달할 수 있으므로 위의 예제는 다음과 같이 간단하게 작성할 수 있습니다.

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

주의:바디에 데이터 청크를 작성하기 전에 상태 코드와 헤더를 설정해야 합니다.
HTTP 응답에서 바디 전에 헤더가 있으므로 이는 이치에 맞습니다.

오류에 대한 추가 설명
response 스트림도 'error' 이벤트를 발생시킬 수 있고 때로는 이 오류도 처리해야 합니다. request 스트림 오류에 대한 모든 설명이 여기서도 똑같이 적용됩니다.

지금까지 배운 내용을 사용한 예제

2-3. preflight request 핸들링

: createServer의 콜백함수 중에 해당 내용을 넣는다

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
});

//서버의 상태코드, 명시적으로 설정할 헤더
(response에 추가해 어떠한 오리진, 메소드의 request든 받아준다)

유의할 점
: preflight request를 하면 OPTIONS 메소드를 사용하지만
서버에서 라우팅 중 method === OPTIONS인 경우에만 사용하면 다른 요청을 했을 때 오리진이 달라 CORS가 발생한다(발생? 발동? 제어?)
그러므로 아예 createServer의 콜백 함수 첫줄부터 넣어주는 거 추천
(모든 라우팅 안에 넣는 방법도 있지만 비효율적이다)

  1. 라우팅에 대한 각각의 응답

보내는 방법 1)

response.write(보낼 내용)
response.end()

보내는 방법 2)

response.end(보낼 내용)

end()는 끝내버리는 용도라 write를 쓴 다음에 꼭 end()를 쓰거나
둘을 합쳐서 end()로 한방에 내용을 보내고 + 끝내도 된다

  1. 에러 핸들링

request 스트림의 오류가 발생하면 스트림에서 'error' 이벤트가 발생하면서 오류를 전달합니다.
이벤트에 리스너가 등록되어 있지 않다면 Node.js 프로그램을 종료시킬 수도 있는 오류를 던질 것입니다.
그러므로 단순히 오류를 로깅만 하더라도 요청 스트림에 'error' 리스너를 추가해야 합니다.
(하지만 HTTP 오류 응답을 보내는 것이 좋을 겁니다.)

response.statusCode = 404
response.end()

예제 설명

http 모듈을 사용하고 있다
createServer
request, response
end, writehead 등 공식 문서 확인하기

서버를 띄운 다음 정보를 보내본다
server/basic-server.js
네트워크 탭을 통해 확인을 한다
options는 cors를 관리하는 부분

server에서 /upper, lower를 구현해야 한다
나머지는 다 error로 만들어야 한다

if메소드가 post, url이 upper이면
대문자 응답

post, url이 lower면
소문자 응답

options면
cors 설정을 돌려줘야 한다

CORS
애플리케이션을 보호하기 위한 것들
다른 extensions들을 통해 해제할 수도 있다

same origin request : 같은 곳에서 리소스
corss origin request : 다른 곳에서 리소스

request.headers
'access-control-request'~~ :
'origin' :
=> response와 달라서 corss origin 필요
=> response.writeHead(200, {method, headers allow하는 것들 설정해서 명시})

Sprint review

buffer
preflight request

HTTP

요청 메소드(GET, POST, DELETE, OPTIONS, PUT) 엔드포인트 (/upper, /lower)
응답 상태코드(200, 300, 400, 500)

node.js로 만든 페이지를 실행할 때 [node 파일명]

server 객체

const server = http.createServer()까지만 만들면 실행될 수 없다
listen 메서드가 server객체에서 호출되어야 한다

server.listen(포트번호, () => {
console.log();
})

http 요청마다 createServer에 전달된 함수가 한번씩 호출된다
ex. 브라우저(클라이언트) - 서버 url 검색 (get 요청)

하지만 get요청을 하면 응답이 없으니까 무한 로딩
브라우저에서 url을 통해 해도 응답이 없으니까 빈 페이지

그러므로 서버 측에서 response.end('response'); 등 응답을 보내야 한다
그럼 브라우저에도 응답이 뜬다

요청에 따라 다른 응답을 받을 수 있도록 나눈다
const {method, url} = request;

cf.npx nodemon app.js //알아서 저장하고 서버 돌려주는 역할

method는 항상 일반적인 HTTP 메서드를 이용한다

node inspector를 사용해서 확인을 한다
request에는 body가 없다 : request.body로 쓸 수 없다

express에서는 더 쉽게 받을 수 있다

error 처리

let body =[];
request.on('data', (chunk) => {
body.push(chunk)//chunk는 Buffer의 형태다
}) //이벤트 리스너

data : data가 올 때 ~하겠다
end : data가 끝날 때 ~하겠다
//이 둘은 정해져 있는 내장 메소드

Buffer

stream이란?
cf. streaming 서비스
유튜브 동영상이 1기가라면 데이터가 한번에 오는 것이 아니라 쪼개져서 온다
data, (chunk) => {chunk}
이 chunk로 쪼개져서 오는데 이는 buffer의 형태다
Buffer는 타입이 buffer다
body라는 빈 배열에 이 chunk(buffer)들이 들어가 있다
Buffer.concat()으로 해서 모두 합치고 toString으로 만든다

Buffer.concat()는 함수
ex. Object.assing같은

CORS

fetch로 url에 요청을 보냈는데 서버가 client(3000)을 허용하지 않은 것
port 번호가 달라서!

ajax(fetch)로 가져오는 건 same origin policy
단점: 서버간의 상호작용이 어렵고 다른 사이트의 데이터를 받아오지 못한다
ex. 기상청에서 데이터를 받아오고 싶은데 naver와 same origin이 아니니까 무넺

해결방법
cors(cross origin resource sharing)
다른 오리진의 리소스를 공유한다

//면접 때 무조건 나온다!!!!!!!

네트워크 요청을 할 때는 same origin policy인데 다른 오리진에서 리소스를 가져올 수 있도록 허용해주는 것이 cors
: 헤더를 추가해서 allow-access-control-origin 등의 헤더에 가능한 origin을 넣는다
: 프로토콜, 도메인, 포트 모두 같아야 한다


Access-Control-Allow-Origin: '*' //모든 오리진 허용
Access-Control-Max-Age: 10 //preflight 유효 기간 10초

preflight

cors 허용했는지 미리 확인하려고 보내는 요청

http 요청 - 응답이 오고 가기 전에 options라는 메소드로 preflight 요청이 간다
OPTIONS(우리 클라이언트 허용해주나??) - defaultCorsHeader(ㅇㅇ, 우리 access-allow-origin-control:*잖아!)//access-allow-origin-control만 설정해도 되지만 한번에 하는 것이 간편

응답에서 cors를 하기 위해서는 response.writehead(200, defaultCorsHeader)
=> 원칙상 모든 요청에 대해 설정을 해줘야 한다
=> response.end, write 위에 설정을 해야 한다
이런 헤더를 실어서 보내야 함

status, cors 정책을 함께 줄 필요는 없지만 상태가 이렇고 헤더는 이렇다라고 넣는 게 좋다
네트워크에서 보면 method를 확인할 수 있다 (options를 확인할 수 있는 개발자 도구가 있고 아닌게 있다)

응답만 보내도 작동은 되는데 writeHead로 명시적으로 보여주는 것이 좋다는 의미

cf. 공부할 때 중요한 것만 보는 전략 (전체적으로 한번 쭉 만들어보고 그 다음에 세부적으로 찾아보는 것이 더 좋음)

OPTIONS는 cors 설정, header 설정을 위해 사용한다고 봐도 된다

writeHead의 명시적, 암묵적

JSON 문법 오류


response.end('codestates')로 보내면 json 문법에 맞지 않다고 보낸다

클라이언트에서 JSON.stringify(body)로 서버에 요청을 보낸다
그럼 서버에서는 response.end(JSON.stringify('codestates')) 식으로 보내야 한다
ajax에서는 요청, 응답을 json으로 보내준다
요청 : JSON.stringify해서 보낸다
응답 : JSON.parse()
하지만 request의 body는 특이한 부분이 있어서 JSON처리를 따로 해줄 필요가 없다
원래는 JSON.parse로 정보를 받고 보낼 때 JSON.stringify로 해야 한다

(postman은 fetch가 아니라서 잘 받음)

Express

설치
hello world
생성기 보지 말기
기본 라우팅 보고
라우팅 보고
middleware 작성, 사용 보고

중요한 파트 express.json!!!

mini node server + express refactoring => statesairline server

profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글