[SOPT] 2차 세미나 - 비동기 처리, HTTP, REST API, Express
📌 이 글은 THE SOPT 30기 서버파트 2차 세미나에서 학습한 내용을 다룹니다.
비동기 처리
동기 vs 비동기
-
동기(Synchronous)
- 어떤 작업이 끝나기 전까지 다음 작업 실행 X
- 요청에 대한 응답이 완료될 때까지 기다림
- 백그라운드 작업 완료 여부 계속 확인
-
비동기(Asynchronous)
- 작업의 완료 유무와 상관없이 동시에 다음 작업 수행 가능
- 백그라운드가 완료 알림을 주면 미리 지정한 이벤트 처리
Node는 대부분의 메서드를 비동기 방식으로 처리합니다.
비동기 방식을 사용하는 이유
Javascript는 기본적으로 Single Thread 언어이기에 한번에 한 작업 처리
Multi Thread - 병렬처리
- 요청을 Thread에서 처리하도록 CPU 자원을 분할
- 자원은 한계가 존재하기 때문에 Load Balancing, 서버 업그레이드 등 자원 문제 해결 필요
비동기 처리가 필요한 이유
- 동기 처리 시 백그라운드가 작업하는 동안 메인 스레드는 대기
- 메인 스레드가 일하지 않고 노는 시간 발생 -> 비효율적
- ex. DB에서 데이터 가져오기
Node에서는?
- Node에서는 비동기 처리를 Thread로 해결하지 않음
- Event 방식으로 해결!
비동기 처리 방식
Callback Function(콜백 함수)
-
어떤 이벤트 발생 시, 특정 시점에 도달했을 때 시스템에서 호출하는 함수, 다른 함수의 인자로서 사용
-
문제점 - Callback Hell 콜백 지옥
setTimeout((name): void => { serverList = [...serverList, name]; console.log(serverList); setTimeout((name): void => { serverList = [...serverList, name]; console.log(serverList); setTimeout((name): void => { serverList = [...serverList, name]; console.log(serverList); }, 500, '서호영'); }, 500, '이동현'); }, 500, '채정아');
Promise
-
Callback Hell 극복 위해 ES2015부터 Node, JS API들이 콜백 대신 Promise로 재구성됨
-
Promise의 3가지 상태
- Pending(대기) - 비동기 처리가 완료되지 않은 상태
- 최초 생성 시점
new Promise()
호출 시 콜백함수 선언- resolve: 동작에 대한 결과를 올바르게 줄 수 있음
- reject: 동작을 실패하면 호출
- Fulfilled(이행) - 비동기 처리가 완료되어 Promise 결과 반환
- 작업을 성공적으로 완료
resolve()
실행이 정상적으로 이행(fulfilled)된 상태then()
을 통해 전달
- Rejected(실패) - 실패하거나 오류
- 작업이 실패한 상태
catch()
를 통해 전달
- Pending(대기) - 비동기 처리가 완료되지 않은 상태
-
Promise Chaining
-
여러개의 Promise를 연결하여 사용
-
<Promise>.then()
: 비동기 작업 완료 시 결과에 따라 함수 호출 -
<Promise>.catch()
: 체이닝 연결 상태에서 중간에 에러가 났을 때 호출const restaurant = (callback: () => void, time: number) => { setTimeout(callback, time); }; const order = (): Promise<string> => { return new Promise((resolve, reject) => { restaurant(() => { console.log('[레스토랑 진행 상황 - 음식 주문]'); resolve('음식 주문 시작'); }, 1000); }); }; const cook = (progress: string): Promise<string> => { return new Promise((resolve, reject) => { restaurant(() => { console.log('[레스토랑 진행 상황 - 음식 조리 중]'); resolve(`${progress} -> 음식 조리 중`); }, 2000); }); }; const serving = (progress: string): Promise<string> => { return new Promise((resolve, reject) => { restaurant(() => { console.log('[레스토랑 진행 상황 - 음식 서빙 중]'); resolve(`${progress} -> 음식 서빙 중`); }, 2500); }); }; const eat = (progress: string): Promise<string> => { return new Promise((resolve, reject) => { restaurant(() => { console.log('[레스토랑 진행 상황 - 음식 먹는 중]'); resolve(`${progress} -> 음식 먹는 중`); }, 3000); }); }; order() .then((progress) => cook(progress)) .then((progress) => serving(progress)) .then((progress) => eat(progress)) .then((progress) => console.log(progress));
출력 [레스토랑 진행 상황 - 음식 주문] [레스토랑 진행 상황 - 음식 조리 중] [레스토랑 진행 상황 - 음식 서빙 중] [레스토랑 진행 상황 - 음식 먹는 중] 음식 주문 시작 -> 음식 조리 중 -> 음식 서빙 중 -> 음식 먹는 중
-
Promise는 여러개여도 catch는 단일!
Promise.resolve(123) .then((res) => { throw new Error('에러 발생'); return 456; }) .then((res) => { console.log(res); // 절대 실행 되지 않음!!! return Promise.resolve(789); }) .catch((error) => { console.log(error.message); });
출력 에러 발생
-
Async/Await
-
ES2017부터 제공되는 혁신적인 기능!
-
Promise가 어느정도 콜백 지옥을 해결했지만, 여전히 코드가 복잡하고 어려움
-
동기 코드와 아주 유사
-
이해하기 쉬움
-
Async: Async는 암묵적으로 Promise를 반환
-
Await: Promise를 기다림(resolve/reject), async 정의된 내부에서만 사용
-
Async 함수 형태
// 함수 표현식 const asyncFunction1 = async() => { } // 함수 선언식 async function asyncFunction2() { }
HTTP
HyperText Transfer Protocol
하이퍼텍스트 문서를 주고 받을 수 있는 프로토콜 (규칙)
Stateless Protocol
무상태 프로토콜
- 모든 요청이 상호 독립적
- 서버가 request, response 간에 어떠한 데이터도 보존하지 않음
- 중간에 요청이 다른 서버로 들어가도 전혀 문제 없음
HTTP Request
HTTP Method
HTTP Method | Action | Request Body |
---|---|---|
GET | 조회 | X |
POST | 생성 | O |
PUT | 수정 | O |
PATCH | 일부 수정 | O |
DELETE | 삭제 | X |
HTTP Response
HTTP Status
응답 코드 | 상태 |
---|---|
200 | 성공 OK |
201 | 성공, 리소스 생성 Created |
204 | 성공, 응답 데이터 없음 No Content |
400 | 요청을 이해하지 못함 Bad Request |
401 | 인증 필요 Unauthorized |
403 | 요청 거부 Forbidden |
404 | 리소스를 찾을 수 없음 Not Found |
409 | 요청 충돌 Conflict |
500 | 서버 내부 오류 Internal Server Error |
503 | 일시적 서버 이용 불가 Service unavailable |
HTTP Request Response
REST API
REpresentational State Transfer
서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 방법
리소스 지향 아키텍처 -> 모든 것을 가급적 리소스, 명사로 표현
REST 아키텍처를 준수하는 API
API
Application Programming Interface
서버 어플리케이션의 기능을 사용하기 위한 방법
구현 방식을 몰라도 서비스가 서로 통신 가능!
그래서 REST API?
URI는 정보의 자원을 표현
자원에 대한 행위는 HTTP Method(GET, POST, PUT, PATCH, DELETE)으로 표현
URI vs URL
URI
Uniform Resource Identifier
통합 자원 식별자 (자원을 나타내는 주소)
자원을 나타내는 유일한 주소
URL
Uniform Resource Locator
통합 자원 지시자
특정 서버의 한 리소스에 대한 구체적인 위치 서술
REST API 기준
- 클라이언트, 서버 및 리소스로 구성되었으며 요청이 HTTP를 통해 관리되는 클라이언트-서버 아키텍처
- 스테이트리스(stateless) 클라이언트-서버 커뮤니케이션
-> 요청 간에 클라이언트 정보가 저장되지 않으며, 각 요청이 분리되어 있고 서로 연결되어 있지 않음 - 클라이언트-서버 상호 작용을 간소화하는 캐시 가능 데이터
- 요청된 정보를 검색하는 데 관련된 서버(보안, 로드 밸런싱 등을 담당)의 각 유형을
클라이언트가 볼 수 없는 계층 구조로 체계화하는 계층화된 시스템 - 정보가 표준 형식으로 전송되도록 하기 위한 구성 요소 간 통합 인터페이스
중심 규칙
- '/' (슬래시)는 계층 관계 표현
- '/' (슬래시)는 URI 마지막에 포함되지 않음
https://sopt.org - (O)
https://sopt.org/ - (X) - 가독성을 높이기 위해 '-' 하이픈 사용
https://sopt.org/server-part - (O)
https://sopt.org/serverpart - (X) - 언더바는 URI에 사용하지 않음
https://sopt.org/server-part - (O)
https://sopt.org/server_part - (X) - URI에는 소문자가 적합
https://sopt.org/server-part - (O)
https://sopt.org/serverPart - (X) - 파일 확장자는 URI에 포함시키지 않음
https://sopt.org/appjam.jpeg - (X) - 리소스 간의 연관관계 표현
https://sopt.org/users/{userId}/device
Express
Node.js를 위한 서버 프레임워크
yarn add express
yarn add -D @types/node @types/express
yarn add -D nodemon
tsconfig.json 파일 생성
tsc --init
Typescript를 Javascript로 컴파일하는 옵션 설정
{
"compilerOptions": {
"target": "es6", // 어떤 버전으로 컴파일
"allowSyntheticDefaultImports": true, // default export가 없는 모듈에서 default imports를 허용
"experimentalDecorators": true, // decorator 실험적 허용
"emitDecoratorMetadata": true, // 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원
"skipLibCheck": true, // 정의 파일 타입 체크 여부
"moduleResolution": "node", // commonJS -> node 에서 동작
"module": "commonjs", // import 문법
"strict": true, // 타입 검사 엄격하게
"pretty": true, // error 메시지 예쁘게
"sourceMap": true, // 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성
"outDir": "./dist", // 트랜스 파일 (.js) 저장 경로
"allowJs": true, // js 파일 ts에서 import 허용
"esModuleInterop": true, // ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용
"typeRoots": [
"./src/types/express.d.ts", // 타입(*.d.ts)파일을 가져올 디렉토리 설정
"./node_modules/@types" // 설정 안할시 기본적으로 ./node_modules/@types
]
},
"include": [
"./src/**/*" // build 시 포함
],
"exclude": [
"node_modules", // build 시 제외
"tests"
]
}
서버 만들어보기
import express, { Request, Response, NextFunction } from 'express';
const app = express(); // express 객체 받아옴
app.get('/', (req: Request, res: Response, next: NextFunction) => {
res.send('Hi! My name is HyeokJoon!');
}); // get -> http method
app.listen('8000', () => {
console.log(`
#############################################
🛡️ Server listening on port: 8000 🛡️
#############################################
`);
}); // 8000 번 포트에서 서버를 실행하겠다 ~
package.json 수정
{
"name": "express-example",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "nodemon",
"build": "tsc && node dist"
},
"dependencies": {
"express": "^4.17.3"
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^17.0.23",
"nodemon": "^2.0.15"
}
}
서버 실행
yarn run dev // nodemon으로 서버 실행
yarn run build // 프로젝트 build
라우팅
애플리케이션 엔드 포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식
import express, { Request, Response, Router } from 'express';
const router: Router = express.Router();
router.get('/', (req: Request, res: Response) => {
return res.status(200).json({
status: 200,
message: '유저 조회 성공',
});
});
module.exports = router;
Author And Source
이 문제에 관하여([SOPT] 2차 세미나 - 비동기 처리, HTTP, REST API, Express), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@orijoon98/SOPT-2차-세미나-저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)