1. require 함수가 리턴하는 객체는 상수로 대입하는 이유
자바스크립트에서 변수(variable)와 상수(constant)의 차이
변수에는 값을 원할 때마다 새롭게 지정해도 되지만, 상수의 경우 값을 한번 설정한 후에는 다른 값을 설정할 수 없다.
왜 그런 걸까?
모듈이 리턴한 객체를 변수로 받으면, 나중에 본인 또는 다른 개발자가 변수 m에 다른 값을 실수로 다시 지정하게 될 수도 있다.
이 경우, 그것 자체만으로는 에러가 발생하지 않지만, 의도하지 않은 오류가 발생할 수 있다는 점에서 오히려 더 위험하다.
하지만 모듈이 리턴한 객체를 상수로 받으면, 상수 m에 새로운 값을 다시 지정하려는 코드 자체에서 에러가 발생하기 때문에, 잘못된 코드를 작성하는 것을 미리 방지할 수 있다.
2. 모듈 내부의 것을 외부에 공개하는 방법
공개하고 싶은 것들 하나씩 공개 : exports
모아서 하나의 객체로 만들고 module.exports 로 객체를 통째로 공개
exports.plus = add; // add 함수를 plus라는 이름으로 공개.
module.exports; // 모두 공개
3. Arrow Function
function add(a, b) {
return a + b;
}
const add = function(a, b) {
return a + b;
};
const add = (a, b) => {
return a + b;
};
화살표가 함수의 몸체 { return a + b; } 를 가리키고 있다. 이런식의 함수를 Arrow function 이라고 한다.
4. 모듈 종류
4.1. 코드 모듈
모듈의 예 : express, os
node.js는 자바스크립트 실행 환경이다.
그럼 node.js랑 크롬이나 사파리 같은 브라우저랑은 그럼 뭐가 다르냐?
API(어떤 플랫폼이나 실행환경 등에서 제공하는 인터페이스)가 다르다.
예를 들어 UI를 제공해야 하는 크롬과 들리 node.js는 그런게 없다.
4.2. 서드파티 모듈
$ npm install cowsay
를 치면 cowsay라는 모듈이 설치된다.
동시에 node_modules, package-lock.json 도 설치가 된다.
const cowsay = require("cowsay");
를 통해 이용할 수 있다.
그럼 서드파티 모듈은 도대체 어디에 설치되는 걸까?
package-lock.json 파일 안에 들여다보면 서드파티 모듈에 관한 정보를 확인할 수 있다.
package-lock.json 파일 안에 "dependencies"라는 필드에는, 현재 디렉토리에 설치된 서드파티 모듈들의 정보가 담겨있다.
'의존하는 것들' 이라는 뜻처럼 우리의 프로젝트가 이 서드파티 모듈들에 의존하고 있기 때문에 이 필드안에 그 정보들이 담긴 것이다.
cowsay 필드 안에 requires 필드를 보면 get-stdin 같은 모듈에 의지하고 있는 것을 볼 수 있다.
즉, 하나의 서드파티 모듈이 설치될 때는 그것이 의존하는 다른 서드파티 모듈들도 함께 설치된다.
그럼 ansi-regex는 왜 설치되어 있는 걸까? cowsay의 requires 필드에 보이지 않는데 말이다.
이것은 cowsay가 ansi-regex라는 서드파티 모듈에 간접적으로 의존하고 있기 때문이다.
cowsay가 의존하는 모듈인 string-width은 ansi-regex를 의존한다.
cowsay → string-width → ansi-regex
이렇게 계단식으로 함께 설치되는 것이다.
4.3. node_modules
package-lock.json이 어떤 서드파일 모듈들이 설치되었는지에 관한 정보를 담고 있다면, node_modules 에는 서드파티 모듈들이 실제로 설치되는 공간이다.
디렉토리가 설치될 수도 있고, 파일만 설치될 수도 있다.
5. 비동기 프로그래밍
console.log('Start');
fs. readFile('./new', 'utf8', (error, data) => {
console.log(data);
});
console.log('Finish');
하면
Start
Finish
file.js
처럼 출력된다. 순서가 잘못된거 같은데 왜그럴까?
동기적이다 : 주문하고 음식이 나오는게 매우 느린 서브웨이처럼 하나씩 요청한 순서대로 이루어지는 것
비동기적이다 : 피자스쿨처럼 전화로 주문 접수 받고 피자가 완성되면 주는 것, 순서대로 처리되지 않는다.
실행 순서
readFile 함수는 비동기 실행되는 함수이기 때문에 그렇다. 거의 대부분 nodejs는 비동기 실행 함수가 쓰인다.
readFileSync 함수는 동기 실행되는 함수이다.
4번 부분은 콜백이라고 한다.
비동기 실행 정리!
특정 작업이 완료되었을 때, 실행할 콜백을 등록해두고, 바로 다음 코드 실행을 넘기는 것
5.1. 콜백
4번 부분은 콜백이라고 한다.
지금 콜백이 Arrow Function의 형태로 쓰여 있는데, 파일 읽기가 완료되면, 이 콜백이 실행되면서 data 인자로 파일의 내용이 전달된다. 그리고 파일을 읽다가 만약 에러가 발생하면(파일이 존재하지 않는 경우나, 작업 도중 컴퓨터에 이상이 생긴 경우 등) err 인자로 해당 에러 정보를 담은 객체가 전달된다.
참고로, Node.js에서 많은 콜백은 일반적으로 err 인자를 첫 번째 인자로 두고, data와 같이 작업 결과를 나타내는 인자를 더 뒤에 둔다.
const fs = require('fs');
let test = 1; // '첫 번째'
fs.readFile('./new', 'utf8', (err, data) => {
test = 2; // '세 번째'
});
console.log(test); // '두 번째'
위의 코드에서는 1이 출력된다.
1을 출력하고나서 test에 2가 저장된다.
- 막간 컴공 개념
프로세스와 스레드는 프로그램의 실행 흐름에 관한 개념이다!
프로세스가 하나의 실행 흐름이라면, 스레드는 그 안에 있는, 더 작은 단위의 실행 흐름이다.
프로세스와 스레드 예시)
우리가 노트북에서 크롬 브라우저를 실행했을 때를 잠깐 생각해보자.
크롬 브라우저는 노트북에 저장된 하나의 '프로그램(Program)'인데, 우리가 크롬 브라우저 아이콘을 더블 클릭하면, 이 프로그램이 '실행'된다.
이 '실행'이라는 건
(1) 하드디스크(hard-disk)나 SSD에 저장되어 있던 프로그램의 내용을,
(2) 메모리(memory)에 올려서
(3) CPU(Central Processing Unit)가 실행하도록 만드는 것을 의미하는데
크롬 브라우저를 실행하면, 그 실행 흐름으로 하나의 크롬 '프로세스' 라는 것이 생성되고, 그 안에서 하나의 '스레드'가 실행 중인 상태가 된다.
여기서 음악을 틀고, 동영상을 틀고, 게임을 하면 4가지의 스레드가 돌아간다.
이처럼 프로세스 안에서 만들 수 있는 '실행의 최소 단위'가 바로 스레드이다.
5.2 Node js가 딥러닝에 적합하지 않는 이유 :
Node.js의 메인 스레드는 긴 작업을 끝내기 전에는 다른 클라이언트의 요청을 처리하지 못하게 된다. File I/O 작업만큼은 빠르게 다른 스레드에 맡길 수 있었지만, CPU 수치 계산 작업은 메인 스레드에서 수행하도록 설계되었기 때문이다. 그래서 다른 클라이언트의 요청이 그만큼 늦게 처리(이미지에서 깃발이 꽂힌 시점부터 가능)되는 결과를 가져온다.
즉, Node.js는 CPU-intensive job(고화질 이미지 처리, 복잡한 시뮬레이션 계산, 딥러닝 작업 등)에는 적절하지 않다. 그리고 혹시라도 이런 job을 처리해야하는 경우라면 해당 작업을 최적화하도록 노력해야 한다. 만약 CPU-intensive job을 메인으로 처리해야하는 서버라면 Node.js보다는 다른 서버 프레임워크 등을 사용하는 게 좋다.
5.3. 이벤트
어떤 일이 발생했음을 알리는 신호
1 : 이벤트 모듈을 불러와서
3 : 객체로 지정하고
5 : test라는 이벤트가 발생하면 Success 를 출력하는 콜백을 만든다.
9 : emit = 발송하다, 전파하다. 즉 이벤트를 발생시킨다.
코어 모듈의 많은 객체들이 결국 EventEmitter 객체라는 것을 알아야 한다.
하나의 이벤트에 관해서 여러개의 콜백들을 설정할 수 있다.
하나의 이벤트에 관해서 여러개의 동작들을 수행할 경우에 이렇게 여러개의 콜백을 만들면 된다.
on() : 이벤트 핸들러를 설정하는 메쏘드
emit() : 인위적으로 이벤트를 발생시키는 메쏘드
once() : 해당 이벤트에 대해서 딱 한번만 반응하는 메쏘드
listeners() : 특정 이벤트에 대한 이벤트 핸들러들을 출력해주는 메쏘드
off() : 이벤트 핸들러를 해제하는 메쏘드
하나의 이벤트에 대해서 여러개의 객체가 적용될 수 있다.
그러나 하나의 이벤트에 대해서
어떤 객체의 콜백이 다른 객체의 콜백 또한 반응하지는 않는다.
따라서 왼쪽 코드는 1, 2만 출력된다.
5.4. 이벤트의 인자는 객체로 받는 것이 좋다.
6. 웹서버
http
클라이언트와 서버가 통신을 하기위해 사용하는 규약
통신 규약, 또는 프로토콜 이라고 한다.
즉, 클라이언트는 어떤 식으로 데이터를 보내주어야 하고, 서버는 어떤 식으로 데이터를 보내주어야 하는지에 대한 규약이다.
클라이언트와 서버가 요청을 주고 받을 때, 항상 프로토콜이 필요하고, 여러가지 프로토콜 중 하나가 http이다.
server.listen(3000)
클라이언트에서 요청이 오는지 귀를 기울이고 있다.
이제 server 객체는 클라이언트로부터 요청을 받아드릴 준비가 되어 있다.
3000 : 포트번호
포트번호란 클라이언트가 서버에 요청을 보내려고 할 때, 서버에서 실행되고 있는 여러 프로그램 중 어느 프로그램과 통신할 것인지를 나타내기 위해 지정하는 번호이다.
이대로 자기 컴퓨터 아이피인 127.0.0.1의 3000번 포트로 들어가보면
아무 반응이 일어나지 않는다.
서버 객체가 클라이언트로부터 요청을 받았을 때 어떤 응답을 주어야 하는지 설정하지 않았기 때문이다.
위의 서버는 포트 번호 3000번을 갖고 실행 중이기 때문에 브라우저에서 포트 번호 3000번을 써주고 접속하였다.
createServer 안에 함수를 만들었다.
이 함수는 서버 객체에 클라이언트의 요청이 들어올 때마다 실행되는 함수이다.
여기에 두가지 인자가 있는데,
request 인자는 클라이언트의 요청에 관한 객체,
response 인자는 서버 객체가 할 응답에 관한 객체이다.
reponse를 통해 HelloWorld를 쳐주고 다시 브라우저로 들어가보면 HellowWorld가 출력된다.
7. url
Unifrom Resource Locator
웹 상의 특정 자원(html, css, js, 이미지 등)의 위치를 나타낸 문자열
http://example.com/business/mart/item?category=14&id=2965
스킴(scheme) : 프로토콜
호스트(host) : 특정서버를 나타낸다.
경로(path) : 원하는 자원의 위치를 나타낸다. 의미를 나타내기 위한 용도일 뿐, 디렉토리 안에 반드시 item 파일이 있어야 하는건 아니다.
쿼리(query) : 서버에 요청을 할 때, 원하는 것을 상세하게 표현하기 위해 사용
잘 조합하면 클라이언트가 서버로부터 정확히 무슨 정보를 원하는지 판단할 수 있다.
도메인 이름을 ip주소로 변환해주는 절차
1단계 : 일단 내 컴퓨터는 기본적으로 설정된 네임 서버(Name Server)에 codeit.kr 의 IP 주소를 알려달라는 요청을 보냅니다. 네임 서버라는 건 도메인 네임을 IP 주소로 변환하는 과정에 참여하는 서버들입니다. 내 컴퓨터에서 맨 처음 어떤 네임 서버에 요청할 것인지는 미리 설정되어 있고, 기존의 설정에서 다른 네임 서버로 바꾸는 것도 가능합니다. 내 컴퓨터가 사용하는 네임 서버에 관한 설정은 OS마다 다른데요. 이 부분은 여러분이 별도로 검색해보기를 추천합니다.
2, 3단계 : 제 컴퓨터의 요청을 받은 네임 서버는 이제 루트 네임 서버(Root Name Server)에 이런 요청을 보냅니다. '.kr'로 끝나는 도메인 네임들을 관리하는 네임 서버의 주소를 알려달라는 요청을요. 그럼 루트 네임 서버는 '.kr' 네임 서버의 IP 주소를 알려줍니다.
4, 5단계 : 그럼 네임 서버는 '.kr' 네임 서버에게 'codeit.kr'의 IP 주소를 알려줄 수 있는, 'codeit.kr' 네임 서버의 IP 주소를 알려달라고 요청합니다. 그럼 '.kr' 네임 서버는 'codeit.kr' 네임 서버의 IP 주소를 알려주죠.
6, 7단계 : 그럼 네임 서버는 'codeit.kr' 네임 서버에게 'codeit.kr'의 IP 주소를 알려달라고 요청합니다. 마침내, 네임 서버는 'codeit.kr'의 실제 IP 주소를 응답으로 얻게 되죠.
8단계 : 네임 서버는 제 컴퓨터에게 codeit.kr의 IP 주소를 알려주고, 제 컴퓨터는 이 IP 주소를 갖고 코드잇 서버와 본격적인 통신을 시작합니다.
방금 살펴본 이런 과정을 조금 어려운 말로, Domain Name Resolution 이라고 합니다. 참고로, 제가 codeit.kr에 접속할 때마다 매번 1부터 8까지의 단계가 항상 발생하는 것은 아닙니다. 왜냐하면 제가 이미 한번 codeit.kr의 IP 주소를 받은 후에는 제 컴퓨터의 OS가 그 IP 주소를 보통 별도로 저장해두고 계속 사용하기 때문입니다. 이뿐만 아니라 제 컴퓨터가 사용하는 가장 근처의 네임 서버 또한 자주 요청받는 도메인 네임에 대해서는 별도로 외부에 요청할 필요가 없도록 캐시(cache)로 관리하는 경우가 많습니다. 즉, 1~8 까지의 과정이 일어나는 경우는 일반적으로 처음 접속하는 도메인 주소인 경우에만 그렇습니다.
7.1. url 쪼개기
http:
example.com
/business/mart/item
?category=14&id=2965
7.2. 포트번호
각 프로토콜마다 포트 번호가 하나씩 할당되어 있다는 걸 알 수 있다.
특정 프로토콜 기반으로 통신을 하는 서버 프로그램은 여기 나온 것처럼 지정된 포트 번호를 갖고 실행되어야 한다.
그리고 많은 서비스들이 이 규칙을 지켜서 서버 프로그램을 실행하고 있다.
사실 https://www.google.com도 https://www.google.com:443 이렇게 써야 정확하고, https://example.com/business/mart/item?category=14&id=2965 도
https://example.com:80/business/mart/item?category=14&id=2965 이렇게 포트 번호를 써줘야 정확한 URL이다.
하지만 포트 번호를 생략해도 맨 앞의 http, https 같은 프로토콜 정보를 보고 브라우저는 자동으로 그에 맞는 포트 번호를 설정하고 서버에 접속을 시도한다. 그렇기 때문에 이때까지 살아오면서 포트 번호를 한 번도 써준 적이 없어도 아무런 문제가 생기지 않았다.
우리가 만들 서버 프로그램은 여러분뿐만 아니라 다른 사람들도 나중에 외부에서 접속할 수 있어야 하고, 이때는 보통 포트 번호 없는 URL로 접속하는 경우가 많기 때문에, 위에서 본 약속대로 포트 번호를 설정하고 서버 프로그램을 실행해야 한다.
아직 개발 단계에 있기 때문에 제가 원하는 아무 포트 번호(3000번)를 쓰고 있는 것뿐이고, 나중에 이 프로그램을 본격적으로 서비스로 배포하려면 당연히 정해진 포트 번호(80번 또는 443번)를 사용해야 한다.
8. 라우팅
요청이 들어온 url에 따라 서버가 다르게 처리하는 것을 라우팅이라고 한다.
길, 경로 또는 보내다, 전송하다라는 뜻을 가지고 있다.
const http = require('http'); // etc
const users = ['Tom', 'Andy', 'Jessica', 'Paul']; // const
const server = http.createServer((request, response) => { // Arrow Function, const
if (request.url === '/') {
response.end('<h1>Welcome!</h1>');
} else if (request.url === '/users') {
response.end(`<h1>${users}/h1>`); // Template String
} else if (request.url.split('/')[1] === 'users') {
// url : /users/1, /users/2, .. // etc
const userIdx = request.url.split('/')[2];
const userName = users[userIdx - 1]; // etc
response.end(`<h1>${userName}</h1>`); // Template String
} else {
response.end('<h1>Page Not Available</h1>');
}
});
server.listen(3000);
문자열들을 변수와 이어붙일 때는 ` ` 백틱을 이용한다.
`<h1> ${userName} </h1>`
이딴거 말고 익스프레스(express)로 라우팅 편하게 하자!
const express = require('express');
const app = express();
const users = ['Tom', 'Andy', 'Jessica', 'Paul'];
app.get('/', (request, response) => {
response.end('<h1>Welcome!</h1>');
});
app.get('/users', (request, response) => {
response.end(`<h1>${users}</h1>`);
});
app.get('/users/:id', (request, response) => {
const userName = users[request.params.id - 1];
response.end(`<h1>${userName}</h1>`);
});
app.get('*', (request, response) => {
response.end('<h1>Page Not Available</h1>');
});
app.listen(3000);
여기서 path 인자로 들어간 *(별표, asterisk) 이 표시의 의미는 무엇일까?
이 표시는 '나머지 모든 URL'을 의미한다고 했는데, 여기서 path가 들어가야 할 자리에 들어간 *는 모든 path를 의미한다.
라우팅에 관해서는 express 편에서 더 알아보자.
'🚦 Server > Node.js' 카테고리의 다른 글
express 시작하기 (0) | 2021.10.28 |
---|---|
서드파티 모듈, npm 이해하기 (1) | 2021.10.26 |
Node - 화살표 함수 (0) | 2021.08.10 |
Node - const, let 는 var를 대체한다. (0) | 2021.08.10 |
Node - 호출 스택, 이벤트 루프 (0) | 2021.08.10 |