2022.8.28.일요일 TIL: 자바스크립트 객체(배열, Map, Set), Nodejs 구현 공부(파일 생성과 리다이렉션)

Dorito·2022년 8월 28일
0

공부 기록

목록 보기
11/71

하루 개요

배열, map, set 차이점 찾아보기 -> js 객체 개념 공부
Nodejs 구현 공부 (챕터 1- 30~ 33장)
운영체제 챕터 5
SQL 기초 문제 리트코드나 프로그래머스에서 풀기

자바스크립트 객체 공부

javascript 에서는 8가지 자료형이 있고, 그 중 7개는 원시형(primitive type), 나머지는 객체형(objective type) 이다.

자바스크립트에는 일반 객체 이외에도 다양한 종류의 객체가 있다.

Array – 정렬된 데이터 컬렉션을 저장할 때 쓰임
Date – 날짜와 시간 정보를 저장할 때 쓰임
Error – 에러 정보를 저장할 때 쓰임
기타 등등

코어 자바스크립트 자료구조와 자료형

객체 (Object)

https://ko.javascript.info/object

객체는 중괄호 {…}를 이용해 만들 수 있다. 중괄호 안에는 ‘키(key): 값(value)’ 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있는데, 키엔 문자형, 값엔 모든 자료형이 허용된다. 프로퍼티 키는 ‘프로퍼티 이름’ 이라고도 부른다.

  1. javascript 에서는 8가지 자료형이 있고, 그 중 7개는 원시형(primitive type), 나머지는 객체형(objective type) 이다.
  2. 객체는 new Object() 으로 생성자를 통해서 만들거나, {}으로 리터럴 문법을 이용해 만들 수 있다.
// 객체 생성법
let user = new Object(); // '객체 생성자' 문법
let user = {};  // '객체 리터럴' 문법 -> 주로 사용
  1. 객체는 key : value 쌍이며, 모든 자료형이 올 수 있다.(객체포함)
  2. 공백이 있는 단어를 key로 사용할 경우, key를 ""로 묶어줘야함. * ex) "hi there"
// 객체 표시
let user = {
  name: "John",
  age: 30,
  "likes birds": true,  // 복수의 단어는 따옴표로 묶어야 합니다.
};
  1. 프로퍼티 끝은 쉼포로 끝날 수 있음. (추가, 삭제, 이동이 쉬워짐)
  2. 상수 객체는 수정될 수 있다.
  • const로 객체가 선언되더라도, 객체변수는 메모리값을 가지고 참조하고 있기 때문에 수정될 수 있다.
  1. 프로퍼티 접근은 점 표기(object.key) 나 대괄호 표기(object[key]) 로 할 수 있는데, 대괄호 표기만 변수를 이용한 런타임환경의 유동적인 접근 이 가능하다.
  2. [계산된 프로퍼티, computed property]이와 유사하게, 프로퍼티 선언시 대괄호 표기를 이용하여 동적으로 프로퍼티를 할당할 수 있다.
  3. [단축 프로퍼티, property value shorthand] 프로퍼티의 이름과 변수명이 동일하면 축약해서 쓸 수 있다.
  4. 프로퍼티의 이름엔 for, let, return 과 같은 예약어를 사용할 수 있다. 단, __proto__ 는 사용 할 수 없다. (해도 안먹힘)
  5. 숫자를 키로 넣으면 자동으로 문자열로 바뀐다.
  6. 존재하지 않는 프로프티에 접근하면 "undefinded"를 반환한다. (확실하게 확인하고 싶으면 "key" in object 써도 좋음)
  7. 프로퍼티는 특별한 방식으로 정렬된다.
  • 정수 프로퍼티(interger property)는 자동으로 오름차순으로 정렬되고
  • 그 외의 프로퍼티는 객체에 추가한 순서 그대로 정렬된다.
  • '정수 프로퍼티’라는 용어는 변형 없이 정수에서 왔다 갔다 할 수 있는 문자열을 의미
    숫자 key를 쓰고 싶은데 자동 오름차순을 적용하고 싶지 않다면 정수가 취급되지 않도록 아래 코드와 같이 속임수 쓰면 됨
let codes = {
  "+49": "독일",
  "+41": "스위스",
  "+44": "영국",
  // ..,
  "+1": "미국"
};

for (let code in codes) {
  alert( +code ); // 49, 41, 44, 1
}

배열

https://ko.javascript.info/array

배열은 순서가 있는 자료를 저장하는 용도로 만들어진 특수한 자료구조이자 객체

키를 사용해 식별할 수 있는 값을 담은 컬렉션은 객체라는 자료구조를 이용해 저장하는데, 객체만으로도 다양한 작업을 할 수 있다.

순서가 있는 컬렉션을 다뤄야 할 때 객체를 사용하면 순서와 관련된 메서드가 없어 그다지 편리하지 않다.
객체는 태생이 순서를 고려하지 않고 만들어진 자료구조이기 때문에 객체를 이용하면 새로운 프로퍼티를 기존 프로퍼티 ‘사이에’ 끼워 넣는 것도 불가능하다.

이럴 땐 순서가 있는 컬렉션을 저장할 때 쓰는 자료구조인 배열을 사용!

다른 언어에서는 메모리 구조로 설명하던데 자스에서는 객체로 보는 것 같다.

살짝 찾아보니 파이썬 같은 경우에는 List로 구현한다고 하고, 자바에서는 선형 자료구조(Data Structure)중 하나로 동일한 타입의 연관된 데이터를 메모리에 연속적으로 저장하여 하나의 변수에 묶어서 관리하기 위한 자료 구조라고 한다!

그리고 자바스크립트 V8엔진에서 배열을 어떻게 처리하는지는 예전에 내가 여기서 정리해놨다.

Iterable 객체

반복 가능한(iterable, 이터러블) 객체는 배열을 일반화한 객체
for..of을 사용할 수 있는 객체를 이터러블이라고 부른다.
배열과 문자열은 Symbol.iterator가 구현되어 있고 가장 광범위하게 쓰이는 내장 이터러블이다.

  • iteration protocol은 iterable protocol과 iterator protocol로 이루어져있다.

iterable: iterator를 반환하는 Symbol.iterator을 가진 값
iterator: {value, done}을 반환하는 next()를 가진 값
여기서 done:true은 반복이 끝났음을 의미하고 그렇지 않은 경우엔 value가 다음 값이 된다.

즉, iterable은 순회 가능한 자료 구조를 뜻하고, iterator는 순회 가능한 자료구조에서 요소를 탐색하기 위한 포인터 역할을 한다.

  • 어떤 객체가 Iterable Protocol을 준수한다면,
  1. 이 객체는 iterable[Symbol.iterator] 메서드를 소유한다.
  2. iterable[Symbol.iterator]메서드를 호출하면, iterator가 반환된다.
  3. 만약 이 객체가 이터레이터 프로토콜을 준수한다면, 반환된 iterator는 next메서드를 갖는다.
  4. 반환된 iterator의 next메서드는, { value, done }프로퍼티를 갖는 객체를 반환한다. 이를 iterator result라고 한다. 이 객체는 순회 가능한 객체에서 포인터 역할을 한다.

반복 가능한 객체(iterable object)는 for...of 구문과 함께 ES2015에서 도입되었다. 반복 가능한 객체를 다른 객체와 구분짓는 특징은, 객체의 Symbol.iterator 속성에 특별한 형태의 함수가 들어있다는 것!

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

iterable이 아닌 일반적인 객체의 경우

Symbol.iterator 메소드를 포함하지 않으므로, for...of 방식으로 순회가 불가능하다. (for ...in 방식으로는 순회 가능)

const obj = { a: 1, b: 2 };
for (const char of obj) {
  console.log(char); 
  // TypeError: obj is not iterable
}


for (const char in obj) {
  console.log(char) // a, b
}

어떤 객체가 Iterable이라면, 그 객체에 대해서 아래의 기능들을 사용할 수 있다.

  1. for...of 루프
  2. spread 연산자 (...)
  3. 분해대입(destructuring assignment)
  4. 기타 iterable을 인수로 받는 함수
// `for...of`
for (let c of 'hello') {
  console.log(c);
}

// spread 연산자
const characters = [...'hello'];

// 분해대입
const [c1, c2] = 'hello';

// `Array.from`은 iterable 혹은 array-like 객체를 인수로 받습니다.
Array.from('hello');
  • for..of loop
// an array is an iterable
var arr = [ 10, 20, 30 ];

for (let val of arr) {
    console.log(`Array value: ${ val }`);
}
// Array value: 10
// Array value: 20
// Array value: 30

메서드 Symbol.iterator는 for..of에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능하다.

  • Spread operator 사용
// spread an iterator into an array,
// with each iterated value occupying
// an array element position.
var vals = [ ...it ];

전개 구문 (spread 프로퍼티인 경우 제외) 은 iterable 객체에만 적용

const arr = [1, 2, 3, 4, 5];
const arr2 = [11, 12, 13, 14, 15];
const arr3 = [...arr, 6, 7, 8, 9, 10, ...arr2];
console.log(arr3); // [1, 2, 3, 4, 5, ..., 15]
  • 이터러블과 유사 배열
    인덱스와 length 프로퍼티가 있는 객체는 유사 배열이라 불린다. 유사 배열 객체엔 다양한 프로퍼티와 메서드가 있을 수 있는데 배열 내장 메서드는 없다!

이터러블(iterable): 메서드 Symbol.iterator가 구현된 객체
유사 배열(array-like): 인덱스와 length 프로퍼티가 있어서 배열처럼 보이는 객체

문자열은 이터러블 객체이면서 유사배열! (for..of 를 사용할 수 있음 + 숫자 인덱스와 length 프로퍼티가 있음)

이 경우는 유사 배열 객체이긴 하지만 이터러블 객체가 아니다.

let arrayLike = { // 인덱스와 length프로퍼티가 있음 => 유사 배열
  0: "Hello",
  1: "World",
  length: 2
};

// Symbol.iterator가 없으므로 에러 발생
for (let item of arrayLike) {}

Array.from() 메서드

이터러블과 유사 배열은 대개 배열이 아니기 때문에 push, pop 등의 메서드를 지원하지 않는다.
어떻게 하면 이터러블과 유사 배열에 배열 메서드를 적용할 수 있을까?
-> Array.from 범용 메서드 사용

Array.from() 메서드는 유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운 Array 객체를 만듭니다.

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};

let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (메서드가 제대로 동작)

syntax: Array.from(arrayLike[, mapFn[, thisArg]])

궁금증 1 : forEachfor of 차이

https://stackoverflow.com/questions/18508786/for-each-vs-iterator-which-will-be-the-better-option

for-each is syntactic sugar for using iterators 라고 한다!!! 그리고 for-each는 collection을 수정할 수 없고 Iterator는 collection 수정이 가능하니 데이터를 단순히 가져오는 용도라면 가독성이 더 좋기 때문에 for-each를 사용하라고 한다.


https://www.geeksforgeeks.org/iterator-vs-foreach-in-java/

궁금증 2: For vs forEach() vs for/in vs for/of in JavaScript

궁금증 4: spread operator는 깊은 복사? 얕은 복사?

mdn 문서

var arr = [1, 2, 3];
var arr2 = [...arr]; // arr.slice() 와 유사
arr2.push(4);

// arr2 은 [1, 2, 3, 4] 이 됨
// arr 은 영향을 받지 않고 남아 있음

1 depth 까지는 확실하게 Deep copy.
2 depth 이상이면 Shallow copy.

나중에 getter, setter에 대해서도 찾아봐야겠다!


맵(Map)과 셋(Set)

https://ko.javascript.info/map-set

위에서는 객체, 배열, iterator 개념에 대해서 알아보았다!

객체 – 키가 있는 컬렉션을 저장함
배열 – 순서가 있는 컬렉션을 저장함

맵(Map)은 키가 있는 데이터를 저장한다는 점에서 객체와 유사하다
다른 점:
맵은 객체와 달리 키를 문자형으로 변환하지 않는다. 키엔 자료형 제약이 없다. 심지어 키로 객체를 허용함
객체는 프로퍼티 순서를 기억하지 못하는 것과는 다르게 맵은 값이 삽입된 순서대로 순회를 실시한다.

주로 어떨 때 쓰냐면
1. Key: key값으로 primitive value을 써야할 때는 map을 사용 (number, object, boolean, NaN, etc)
2. Order: insertion order를 기억해야할 때는 map을 사용
3. Size: map에서는 쉽게 size를 얻을 수 있다. (Object에서는 Object.keys(obj).length로 접근가능)

let map = new Map();

map.set('1', 'str1');   // 문자형 키
map.set(1, 'num1');     // 숫자형 키
map.set(true, 'bool1'); // 불린형 키

/* 객체는 키를 문자형으로 변환
   그와 달리 맵은 키의 타입을 변환시키지 않고 그대로 유지한다
   따라서 아래의 코드는 출력되는 값이 다르다. */
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3
  • 맵의 주요 메서드
new Map() – 맵 객체 생성
map.set(key, value) – key를 이용해 value를 저장
map.get(key) – key에 해당하는 값을 반환 key가 존재하지 않으면 undefined를 반환
map.has(key) – key가 존재하면 true, 존재하지 않으면 false를 반환
map.delete(key) – key에 해당하는 값을 삭제
map.clear() – 맵 안의 모든 요소를 제거
map.size – 요소의 개수를 반환

이때 map.set 호출할 때마다 map 자기자신이 호출되므로 체이닝(chaining) 가능함

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');
  • 주의할 점
    map[key]는 Map을 쓰는 바른 방법이 아니다.
    map[key] = 2로 값을 설정하는 것 같이 map[key]를 사용할 수 있기는 한데, 이 방법은 map을 일반 객체처럼 취급하게 된다. 따라서 여러 제약이 생기게 된다. (그러면 map의 특성을 제대로 쓸 수 없다.)
    map을 사용할 땐 map전용 메서드 set, get 등을 사용해야만 함!
  • map key값에 객체 쓰기
let john = { name: "John" };

// 고객의 가게 방문 횟수를 세본다고 가정해 봅시다.
let visitsCountMap = new Map();

// john을 맵의 키로 사용하겠습니다.
visitsCountMap.set(john, 123);

alert( visitsCountMap.get(john) ); // 123

객체의 경우, 모든 키를 문자형으로 변형시키기 때문에 john 객체를 키값으로 삽입할 경우, "[object Object]" 로 바뀐다.

  • Iterating Map with for..of, forEach()
map.keys() – 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환
map.values() – 각 요소의 값을 모은 이터러블 객체를 반환
map.entries() – 요소의 [키, 값]을 한 쌍으로 하는 이터러블 객체를 반환. 
이 이터러블 객체는 for..of반복문의 기초로 쓰임
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// 키(vegetable)를 대상으로 순회합니다.
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// 값(amount)을 대상으로 순회합니다.
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// [키, 값] 쌍을 대상으로 순회합니다.
for (let entry of recipeMap) { 
  // recipeMap.entries()와 동일합니다.
  alert(entry); // cucumber,500 ...
}
  • 객체 <-> 맵 바꾸기

객체 -> 맵: Object.entries() mdn 문서

let obj = {
  name: "John",
  age: 30,
};

let map = new Map(Object.entries(obj));

console.log(map);
console.log(map.get("name")); // John

맵 -> 객체: Object.fromEntries() mdn 문서

const array = [
  ["banana", 1],
  ["orange", 2],
  ["meat", 4],
];

const prices = Object.fromEntries(array);

console.log(prices);

// 출력: { banana: 1, orange: 2, meat: 4 }

참고 사이트: https://velog.io/@dolarge/Java-Script-Set-%EA%B3%BC-Map
https://joshua1988.github.io/vue-camp/js/collection.html#map-%E1%84%87%E1%85%A1%E1%86%AB%E1%84%87%E1%85%A9%E1%86%A8%E1%84%86%E1%85%AE%E1%86%AB

셋(Set)은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션.
set 객체는 다음과 같은 특징을 가진다.
셋에 키가 없는 값이 저장된다.
동일한 값을 중복하여 포함할수 없다
요소 순서에 의미가 없다
인덱스로 요소에 접근할 수 없다.
Set을 통해 교집합, 합집합, 차집합 등 수학적 집합 표현이 가능하다

  • 주요 메서드
new Set(iterable) 
– 셋을 만듭니다. 이터러블 객체를 전달받으면(대개 배열을 전달받음) 
그 안의 값을 복사해 셋에 넣어준다.

set.add(value) – 값을 추가하고 셋 자신을 반환

set.delete(value) – 값을 제거 
호출 시점에 셋 내에 값이 있어서 제거에 성공하면 true, 아니면 false를 반환

set.has(value) – 셋 내에 값이 존재하면 true, 아니면 false를 반환

set.clear() – 셋을 비운다

set.size – 셋에 몇 개의 값이 있는지 세준다.

맵과 셋에 반복 작업을 할 땐, 해당 컬렉션에 요소나 값을 추가한 순서대로 반복 작업이 수행된다. 따라서 이 두 컬렉션은 정렬이 되어있지 않다고 할 수는 없다. 그렇지만 컬렉션 내 요소나 값을 재 정렬하거나 (배열에서 인덱스를 이용해 요소를 가져오는 것처럼) 숫자를 이용해 특정 요소나 값을 가지고 오는 것은 불가능함.

  • Iterating Set with for..of, forEach()
let set = new Set(["oranges", "apples", "bananas"]);
console.log(set[Symbol.iterator]); // [Function: values]

set 도 iterator로 for ...of, forEach 사용 가능함.

forEach() mdn 문서

arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])

const set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) {
  console.log("for of", value);
}

// forEach를 사용해도 동일하게 동작합니다.
set.forEach((value, valueAgain, set) => {
  console.log("forEach", value);
});

흥미로운 점! forEach에 쓰인 콜백 함수는 세 개의 인수를 받는데, 첫 번째는 값, 두 번째도 같은 값인 valueAgain, 세 번째는 목표하는 객체(셋)이다.

동일한 값이 인수에 두 번 등장하고 있는 이유는 맵과의 호환성 때문이다.
맵의 forEach에 쓰인 콜백이 세 개의 인수를 받을 때를 위해서임.
이상해 보이겠지만 이렇게 구현해 놓았기 때문에 맵을 셋으로 혹은 셋을 맵으로 교체하기가 쉽다고 한다 (이해 제대로 못함)

set.keys() – 셋 내의 모든 값을 포함하는 이터러블 객체를 반환

set.values() – set.keys와 동일한 작업을 합니다. 맵과의 호환성을 위해 만들어진 메서드

set.entries() – 셋 내의 각 값을 이용해 만든 
[value, value] 배열을 포함하는 이터러블 객체를 반환
맵과의 호환성을 위해 만들어짐

Nodejs 구현

1-31 App - 글 생성 ui 만들기

let http = require("http");
let fs = require("fs");
let url = require("url");

function templateHTML(title, list, body) {
  return `
    <!doctype html>
    <html>
        <head>
            <title>WEB1 - ${title}</title>
            <meta charset="utf-8">
        </head>
        <body>
            <h1><a href="/">WEB</a></h1>
            ${list}
            <a href="/create">create</a>
            ${body}
        </body>
    </html>
    `;
}

function templateList(filelist) {
  let list = "<ul>";
  for (let i = 0; i < filelist.length; i++) {
    list = list + `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
  }
  list = list + "</ul>";
  return list;
}

let app = http.createServer(function (request, response) {
  let _url = request.url;
  let queryData = url.parse(_url, true).query;
  let pathname = url.parse(_url, true).pathname;

  if (pathname === "/") {
    if (queryData.id === undefined) {
      fs.readdir("./data", function (error, filelist) {
        let title = "Welcome";
        let description = "Hello, Node.js";
        let list = templateList(filelist);
        let template = templateHTML(
          title,
          list,
          `<h2>${title}</h2><p>${description}</p>`
        );
        response.writeHead(200);
        response.end(template);
      });
    } else {
      fs.readdir("./data", function (error, filelist) {
        fs.readFile(
          `data/${queryData.id}`,
          "utf8",
          function (err, description) {
            let title = queryData.id;
            let list = templateList(filelist);
            let template = templateHTML(
              title,
              list,
              `<h2>${title}</h2><p>${description}</p>`
            );
            response.writeHead(200);
            response.end(template);
          }
        );
      });
    }
  } else if (pathname === "/create") {
    fs.readdir("./data", function (error, filelist) {
      let title = "WEB - create";
      let list = templateList(filelist);
      let template = templateHTML(
        title,
        list,
        `
        <form action="http://localhost:3000/create_process" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p>
                <textarea name="description" placeholder="description"></textarea>
            </p>
            <p>
                <input type="submit">
            </p>
        </form>
        `
      );
      response.writeHead(200);
      response.end(template);
    });
  } else {
    response.writeHead(404);
    response.end("Not found");
  }
});
app.listen(3000);


<form action="http://localhost:3000/create_process" method="post"> // 생략 </form>
submit 버튼 누르면 Network 탭에 create_process 이름의 행이 뜬다.

create_process는 아직 처리하지 않았기 때문에 상태코드 404 Not Found가 뜬다.

url에 나타나는 대신 payload에 title, description 값이 POST 값으로 전송되는 것을 볼 수 있다.

1-32 App- POST 방식으로 전송된 데이터 받기

POST 방식으로 전송된 데이터를 가져와서 title과 description 변수에 담기

POST 형태의 요청은 GET과는 요청하는 측의 데이터 구조가 다르다.
GET의 경우는 같이 주소의 끝에 ?(물음표)를 붙이고 뒤에 변수명=값 형태로 요청
POST는 주소만 요청하고 변수와 값을 주소가 아닌 요청 BODY에 담아서 서버측에 요청한다.

https://stackoverflow.com/questions/4295782/how-to-process-post-data-in-node-js

let http = require("http");
let fs = require("fs");
let url = require("url");
let qs = require("querystring");

function templateHTML(title, list, body) {
  return `
    <!doctype html>
    <html>
        <head>
            <title>WEB1 - ${title}</title>
            <meta charset="utf-8">
        </head>
        <body>
            <h1><a href="/">WEB</a></h1>
            ${list}
            <a href="/create">create</a>
            ${body}
        </body>
    </html>
    `;
}

function templateList(filelist) {
  let list = "<ul>";
  for (let i = 0; i < filelist.length; i++) {
    list = list + `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
  }
  list = list + "</ul>";
  return list;
}

let app = http.createServer(function (request, response) {
  let _url = request.url;
  let queryData = url.parse(_url, true).query;
  let pathname = url.parse(_url, true).pathname;

  if (pathname === "/") {
    if (queryData.id === undefined) {
      fs.readdir("./data", function (error, filelist) {
        let title = "Welcome";
        let description = "Hello, Node.js";
        let list = templateList(filelist);
        let template = templateHTML(
          title,
          list,
          `<h2>${title}</h2><p>${description}</p>`
        );
        response.writeHead(200);
        response.end(template);
      });
    } else {
      fs.readdir("./data", function (error, filelist) {
        fs.readFile(
          `data/${queryData.id}`,
          "utf8",
          function (err, description) {
            let title = queryData.id;
            let list = templateList(filelist);
            let template = templateHTML(
              title,
              list,
              `<h2>${title}</h2><p>${description}</p>`
            );
            response.writeHead(200);
            response.end(template);
          }
        );
      });
    }
  } else if (pathname === "/create") {
    fs.readdir("./data", function (error, filelist) {
      let title = "WEB - create";
      let list = templateList(filelist);
      let template = templateHTML(
        title,
        list,
        `
        <form action="http://localhost:3000/create_process" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p>
                <textarea name="description" placeholder="description"></textarea>
            </p>
            <p>
                <input type="submit">
            </p>
        </form>
        `
      );
      response.writeHead(200);
      response.end(template);
    });
  } else if (pathname === "/create_process") {
    let body = '';
    request.on('data', function(data) {
      // 조각조각 나눠서 데이터를 수신할 때마다 호출되는 콜백 함수
      // 데이터를 처리하는 기능을 정의
    });
    
    request.on('end', function() {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수
      // 데이터 처리를 마무리하는 기능을 정의
    });

    response.writeHead(200);
    response.end("Seccess");
  } else {
    response.writeHead(404);
    response.end("Not found");
  }
});
app.listen(3000);
let qs = require("querystring");

querystring 모듈에 있는 parse 기능을 사용하기 위해 querystring 모듈을 가져옴

submit 버튼 누르면 /creat_process 로 이동되고 POST 요청이 감

 else if (pathname === "/create_process") {
    let body = '';
    request.on('data', function(data) {
      // 조각조각 나눠서 데이터를 수신할 때마다 호출되는 콜백 함수
      // 데이터를 처리하는 기능을 정의
    });
    
    request.on('end', function() {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수 (수신 완료)
      // 데이터 처리를 마무리하는 기능을 정의
    });

    response.writeHead(200);
    response.end("Seccess");
  }

빈 문자열로 body 변수 선언하고, request의 on 메서드를 2번 호출함!

request는 main.js에서 createServer 함수의 콜백으로 전달한 인수에서 찾을 수 있음
(var라고 적힌 코드 임의로 let이라구 했는데 별 문제 없겠지..?)

Node js 공식문서 HTTP 트랜잭션 해부 꼭 같이 보기!!!

  • 서버 생성

const http = require('http');
let app = http.createServer(function(request, response) {
 // 내용
})

이 서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출된다.
사실 createServer가 반환한 Server 객체는 EventEmitter이고 여기서는 server 객체를 생성하고 리스너를 추가하는 축약 문법을 사용한 것!

Node.js에서 웹브라우저로 접속할 때마다 Node.js가 createServer의 콜백 함수를 호출한다.
이때 createServer로 2개의 인자를 전달하는데,
1. request: 요청할 때 웹 브라우저가 보낸 정보가 담겨있음 -> POST방식으로 전달받은 정보가 담겨있음
2. response: 응답할 때 웹 브라우저에 전송할 정보를 담음.

  • 요청 바디
    POST나 PUT 요청을 받을 때 애플리케이션에 요청 바디는 중요할 것입니다. 요청 헤더에 접근하는 것보다 바디 데이터를 받는 것은 좀 더 어렵습니다. 핸들러에 전달된 request 객체는 ReadableStream 인터페이스를 구현하고 있습니다. 이 스트림에 이벤트 리스너를 등록하거나 다른 스트림에 파이프로 연결할 수 있습니다. 스트림의 'data'와 'end' 이벤트에 이벤트 리스너를 등록해서 데이터를 받을 수 있습니다.

각 'data' 이벤트에서 발생시킨 청크는 Buffer입니다. 이 청크가 문자열 데이터라는 것을 알고 있다면 이 데이터를 배열에 수집한 다음 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋습니다.

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
});

주의: 이 코드가 약간 장황할 수도 있고 대부분은 실제로 그렇습니다. 다행히 npm에 concat-stream나 body 같은 모듈로 이 로직을 감출 수 있습니다. 이어서 읽기 전에 어떤 작업이 이뤄지는지 잘 이해하는 것이 중요!

  • POST 방식으로 전달받은 정보를 출력
else if (pathname === "/create_process") {
    let body = "";
    request.on("data", function (data) {
      // 조각조각 나눠서 데이터를 수신할 때마다 호출되는 콜백 함수
      // 데이터를 처리하는 기능을 정의
      body = body + data;
     // 콜백으로 전달 받은 인자 data에 담긴 내용을 변수 body에 누적해서 합친다.
    });

    request.on("end", function () {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수
      // 데이터 처리를 마무리하는 기능을 정의
      let post = qs.parse(body);
      console.log(post);
      // qs모듈의 parse기능을 이용해 body에 누적한 내용을 post에 담음
    });

    response.writeHead(200);
    response.end("Seccess");
  }

웹페이지 새로고침하면 콘솔창에

[Object: null prototype] {
  title: 'this is tite',
  description: 'this is description'
}

출력된다!!

근데 예제코드 써보니까 vsc에서 qs모듈이 deprecate 되었다고 알려줬다 (취소선)

구글링해보니까 여기서 잘 알려주셨다.

https://www.npmjs.com/package/querystring 에서 알려줌!

let post = qs.parse(body); 코드 deprecate 되어있어 가지고

let post = new url.URLSearchParams(body) 로 바꿈

    request.on("end", function () {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수
      // 데이터 처리를 마무리하는 기능을 정의
      let post = new url.URLSearchParams(body);
      console.log("title:", post.get("title"));
      console.log("description:", post.get("description"));
    });

-> POST 방식으로 전송된 데이터를 title과 description 변수에 저장해서 콘솔로 출력하는 것까지 성공!!!

1-33 App - 파일 생성과 리다이렉션

사용자가 입력한 정보를 받아서 프로그램 방식으로 동적으로 data 디렉터리에 파일을 생성하고 리다이렉션

https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback

  • 데이터를 파일 형태로 저장하기 위한 writeFile() 함수
    request.on("end", function () {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수
      // 데이터 처리를 마무리하는 기능을 정의
      let post = new url.URLSearchParams(body);
      let title = post.get("title");
      let description = post.get("description");

      fs.writeFile(`data/${title}`, description, 'utf-8', function(err) {
        response.writeHead(200);
        response.end('seccess');
      })
    });
  1. 첫 번째 argument 파일 이름 -> data 디렉터리 아래의 ${title}이 나타내는 값으로 파일 이름을 지정 (= 사용자로부터 전달받은 데이터 중 제목을 파일 이름으로 저장)

  2. 두 번째 argument 파일에 쓸 내용 -> 사용자로부터 전달받은 데이터 중 내용이 담긴 description을 그대로 전달

  3. 세 번째 argument 파일을 저장할 때 UTF-8 인코딩을 사용

  4. 네 번째 argument 콜백 전달 -> 파일 쓰기를 마쳤을 때 내부적으로 자동 호출되는 함수. 이때 err이라는 매개 변수를 전달하는데, 이는 에러가 있을 때 에러를 처리하는 방법을 제공하는 매개변수. 콜백이 실행됐다는 것은 파일에 저장이 끝났다는 의미고 파일 저장이 끝난 후에 seccess를 출력해야 하므로 웹 브라우저에 'seccess'를 응답하는 코드를 씀

=> 결과

대박!!!!!!!!!!!! 신기!!!!! data 디렉터리 안에 파일 생성된다!!!!!!!!!!!!!

https://stackoverflow.com/questions/4062260/nodejs-redirect-url

이 코드는 Header를 302로 응답하고, Location에 있는 주소로 이동하라는 의미!
302는 사용자를 다른 페이지로 이동시키는데, 이 주소가 일시적으로 바뀐 것!
즉, 현재 서버가 다른 위치(/?id=Nodejs)에 있는 페이지로 요청에 응답하고 있지만, 사용자가 나중에 다시 요청할 때는 원래 위치 (create_process)를 계속 사용해야함을 나타낸다!

  • 3xx (리다이렉션 완료)
    301 (영구 이동): 요청한 페이지가 새 위치로 영구적으로 이동. GET 또는 HEAD 요청에 대한 응답으로 이 응답을 표시하면 자동으로 새 위치로 전달된다.
    302 (임시 이동): 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만, 요청자는 향후 요청할 때 원래 위치를 계속 사용해야 한다.

위 스택오버플로우 코드를 참고해서 우리 코드에서 파일 쓰기를 마쳤을 때 웹 페이지를 리다이렉션하게 수정해보자.

  • 리다이렉션 관련 코드
      fs.writeFile(`data/${title}`, description, "utf-8", function (err) {
        response.writeHead(302, { Location: `/?id=${title}` });
        response.end();
      });

writeHead() 메서드 인수 분석:
1. 첫 번째 인수: HTTP 상태 코드로서, 3xx 코드는 리다이렉션을 의미!
2. 두 번째 인수: 리다이렉션하고자 하는 경로 -> 앞에서 생성한 Nodejs 파일을 보여주고 싶으므로 템플릿 리터럴을 이용해 id의 값으로 title을 지정한다.

헐~ 대박 신기하다

동적으로 페이지가 관리됨!!!


완성 코드

let http = require("http");
let fs = require("fs");
let url = require("url");
let qs = require("querystring");
const { title } = require("process");

function templateHTML(title, list, body) {
  return `
    <!doctype html>
    <html>
        <head>
            <title>WEB1 - ${title}</title>
            <meta charset="utf-8">
        </head>
        <body>
            <h1><a href="/">WEB</a></h1>
            ${list}
            <a href="/create">create</a>
            ${body}
        </body>
    </html>
    `;
}

function templateList(filelist) {
  let list = "<ul>";
  for (let i = 0; i < filelist.length; i++) {
    list = list + `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
  }
  list = list + "</ul>";
  return list;
}

let app = http.createServer(function (request, response) {
  let _url = request.url;
  let queryData = url.parse(_url, true).query;
  let pathname = url.parse(_url, true).pathname;

  if (pathname === "/") {
    if (queryData.id === undefined) {
      fs.readdir("./data", function (error, filelist) {
        let title = "Welcome";
        let description = "Hello, Node.js";
        let list = templateList(filelist);
        let template = templateHTML(
          title,
          list,
          `<h2>${title}</h2><p>${description}</p>`
        );
        response.writeHead(200);
        response.end(template);
      });
    } else {
      fs.readdir("./data", function (error, filelist) {
        fs.readFile(
          `data/${queryData.id}`,
          "utf8",
          function (err, description) {
            let title = queryData.id;
            let list = templateList(filelist);
            let template = templateHTML(
              title,
              list,
              `<h2>${title}</h2><p>${description}</p>`
            );
            response.writeHead(200);
            response.end(template);
          }
        );
      });
    }
  } else if (pathname === "/create") {
    fs.readdir("./data", function (error, filelist) {
      let title = "WEB - create";
      let list = templateList(filelist);
      let template = templateHTML(
        title,
        list,
        `
        <form action="http://localhost:3000/create_process" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p>
                <textarea name="description" placeholder="description"></textarea>
            </p>
            <p>
                <input type="submit">
            </p>
        </form>
        `
      );
      response.writeHead(200);
      response.end(template);
    });
  } else if (pathname === "/create_process") {
    let body = "";
    request.on("data", function (data) {
      // 조각조각 나눠서 데이터를 수신할 때마다 호출되는 콜백 함수
      // 데이터를 처리하는 기능을 정의
      body = body + data;
    });

    request.on("end", function () {
      // 더이상 수신할 정보가 없으면 호출되는 콜백함수
      // 데이터 처리를 마무리하는 기능을 정의
      let post = new url.URLSearchParams(body);
      let title = post.get("title");
      let description = post.get("description");

      fs.writeFile(`data/${title}`, description, "utf-8", function (err) {
        response.writeHead(302, { Location: `/?id=${title}` });
        response.end();
      });
    });
  } else {
    response.writeHead(404);
    response.end("Not found");
  }
});
app.listen(3000);

하루 회고

  • 하루 한 것
    배열, map, set 차이점 찾아보기 -> js 객체 개념 공부
    Nodejs 구현 공부 (챕터 1- 30~ 33장)

  • 못한 것
    운영체제 챕터 5
    SQL 기초 문제 리트코드나 프로그래머스에서 풀기

  • 피드백
    운영체제 복습 자꾸 밀린다..

  • 내일 할 것
    Nodejs 구현
    운영체제 챕터 5
    SQL 기초 문제 리트코드나 프로그래머스에서 풀기
    알고리즘 문제 리트코드
    https://leetcode.com/problems/longest-subsequence-with-limited-sum/
    https://leetcode.com/problems/removing-stars-from-a-string/
    풀어보고 싶음
    자료구조.. 공부해야하는데 시간 남으면 보기

0개의 댓글