-
[TIL] 항해 99 4주차 주특기 숙련 회고록_13일차항해[TIL] 2021. 11. 23. 01:28
Today I Learnd
(2021. 11. 22. 월)
목차
- Node.js 심화반 1주차
- Node.js 심화반 2주차
- gRPC 프로토콜
- 4주차 1일차 소감 및 부족한 점
1. Node.js 심화반 1주차
- 할 일 메모하기 사이트 API 연결
기존에 만들어져 있는 프론트 엔드에 Node.js를 이용하여 백엔드를 완성시키는 연습을 복습겸 했다.
const express = require("express"); const bodyParser = require("body-parser"); const mongoose = require("mongoose"); const Todo = require("./models/todo"); mongoose.connect("mongodb://localhost/todo-demo", { useNewUrlParser: true, useUnifiedTopology: true, }); const db = mongoose.connection; db.on("error", console.error.bind(console, "connection error:")); const app = express(); const router = express.Router(); router.get("/", (req, res) => { res.send("Hi!"); }); router.get("/todos", async (req, res) => { const todos = await Todo.find().sort("-order").exec(); res.send({ todos }); }); router.post("/todos", async (req, res) => { const { value } = req.body; const maxOrderTodo = await Todo.findOne().sort("-order").exec(); let order = 1; if (maxOrderTodo) { order = maxOrderTodo.order + 1; } const todo = new Todo({ value, order }); await todo.save(); res.send({ todo }); }); router.patch("/todos/:todoId", async (req, res) => { const { todoId } = req.params; const { order, value, done } = req.body; const todo = await Todo.findById(todoId).exec(); if (order) { const targetTodo = await Todo.findOne({ order }).exec(); if (targetTodo) { targetTodo.order = todo.order; await targetTodo.save(); } todo.order = order; } else if (value) { todo.value = value; } else if (done !== undefined) { todo.doneAt = done ? new Date() : null; } await todo.save(); res.send({}); }); router.delete("/todos/:todoId", async (req, res) => { const { todoId } = req.params; const todo = await Todo.findById(todoId).exec(); await todo.delete(); res.send({}); }); app.use("/api", bodyParser.json(), router); app.use(express.static("./assets")); app.listen(8080, () => { console.log("서버가 켜졌어요!"); });
Node.js 기초반 강의 수강 시 들었던 것들에 대해서 복습겸 실습을 통해서 다시 한번 머리에 되새기게 되었다.
- Validation이란
어떤것을 검증한다고 보면 되고, 개발을 하면서 가장 중요한것중 하나이다.
ex)
function is1(value) { return value === 1; }
위 코드 처럼 값이 1인지 아닌지 확인하기 위해서 Boolean값을 반환하고 있는 함수다.
이러한 함수 처럼 검증하는 코드가 되므로, Validation이라고 볼 수 있다.
- HTTP 인증
엑세스의 제어와 인증을 위한 프레임워크를 제공한다. 기본적인 인증 방식에는 basic방식이있다.
ㄱ. Basic 인증
HTTP 표준의 가장 단순한 인증 방식이다.
사용자의 이름과 암호를 Base64로 인코딩 된 문자열을 보낸다.
간편하며 널리 쓰이는 대신에 패킷 도청에 취약하다.ㄴ. digest 인증
사용자명, 패스워드 등을 조합하여 MD5 값으로 인증한다.
Basic 인증보다는 보안이 강화된 인증방식이다.
서버가 단순히 클라이언트에서 패스워드를 받는 게 아니라, 조합이 된 MD%로 해시값을 받는다.
ㄷ. ssl 클라이언트 인증
HTTPS의 클라이언트 인증서를 사용하여 인증하는 방식이다.
SSL클라이언트 인증만 사용되지 않고, 폼베이스 인증과 함께 사용한다.
단점으로 클라이언트 증명서 구입비용과 서버에서 인증 기관을 만들어서 안전한 보안을 운영하기 위해서
비용 등이 매우 많이 필요하다.
ㄹ. 폼 베이스 인증
HTTP 인증방식 대부분이 폼 베이스 인증이다.
Basic, digest 인증 방식은 사용상의 문제와 보안적인 문제로 거의 사용하지 않는다.
SSL클라이언트 인증 방식도 도입할 때 비용이나 운용비용의 문제로 거의 사용하지 않는다.폼 베이스 인증은 클라이언트에서 자격 정보를 입력하면 서버의 검증 결과에 따라 인증을 하는 방식이다.
웹 페이지마다 제공되는 인터페이스나 인증의 방법이 다양하다.
ㅁ. Bearer 인증 (로그인 및 회원가입 기능 구현 시 사용)
토큰 기반 인증으로 쿠키나 세션을 이용하는 인증 방식 보다 보안성이 강하고,
효율적인 인증 방식이다.
암호화 방식과 타입을 나타내는 헤더,
전송할 데이터가 담긴 페이로드,
변조되지 않은 토큰인지 검증을 위한 서명을 각각 해싱한 값이 포함되어 있다.
2. Node.js 심화반 2주차
- JWT란
JSON 형태의 데이터를 안전하게 교환해서 사용할 수 있게 해준다.
인터넷 표준으로서 자리잡은 규격이다.
여러가지 암호화 알고리즘을 사용할 수 있다.
header.payload.signature의 형식으로 3가지의 데이터를 포함한다.
-JWT +
암호 키를 몰라도 Decoded가 가능하다.
변조만 불가능할 뿐이지 누구나 복호화를해서 보는 것이 가능하다.
민감한 정보들은 담지 않도록 해야된다.
- HTTP 상태 코드
1xx(정보) : 요청을 받아서 프로세스를 계속 진행한다.
2xx(성공) : 요청을 성공적으로 받아서 인식을하고 수용했다.
3xx(리다이렉션) : 요청 완료를 해주기 위해서 작업 조치카 필요하다.
4xx(클라이언트 오류) : 요청의 문법이 잘못되었거나 처리할 수 없다.
5xx(서버 오류) : 서버가 유효한 요청에 대해서 충족하지 못했다.
-스파르타 쇼핑몰 로그인 회원가입 구현
Node.js 기초반에 이어서 만들지 못했던 로그인과 회원가입을 구현에 대한 강의를 듣고 실습을 했다.
const express = require("express"); const mongoose = require("mongoose"); const Joi = require("joi"); const jwt = require("jsonwebtoken"); const User = require("./models/user"); const Goods = require("./models/goods"); const Cart = require("./models/cart"); const authMiddleware = require("./middlewares/auth-middleware"); mongoose.connect("mongodb://localhost/shopping-demo", { useNewUrlParser: true, useUnifiedTopology: true, }); const db = mongoose.connection; db.on("error", console.error.bind(console, "connection error:")); const app = express(); const router = express.Router(); const postUsersSchema = Joi.object({ nickname: Joi.string().required(), email: Joi.string().email().required(), password: Joi.string().required(), confirmPassword: Joi.string().required(), }); router.post("/users", async (req, res) => { try { const { nickname, email, password, confirmPassword, } = await postUsersSchema.validateAsync(req.body); if (password !== confirmPassword) { res.status(400).send({ errorMessage: "패스워드가 패스워드 확인란과 동일하지 않습니다.", }); return; } const existUsers = await User.find({ $or: [{ email }, { nickname }], }); if (existUsers.length) { res.status(400).send({ errorMessage: "이미 가입된 이메일 또는 닉네임이 있습니다.", }); return; } const user = new User({ email, nickname, password }); await user.save(); res.status(201).send({}); } catch (err) { console.log(err); res.status(400).send({ errorMessage: "요청한 데이터 형식이 올바르지 않습니다.", }); } }); const postAuthSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().required(), }); router.post("/auth", async (req, res) => { try { const { email, password } = await postAuthSchema.validateAsync(req.body); const user = await User.findOne({ email, password }).exec(); if (!user) { res.status(400).send({ errorMessage: "이메일 또는 패스워드가 잘못됐습니다.", }); return; } const token = jwt.sign({ userId: user.userId }, "..."); res.send({ token, }); } catch (err) { console.log(err); res.status(400).send({ errorMessage: "요청한 데이터 형식이 올바르지 않습니다.", }); } }); router.get("/users/me", authMiddleware, async (req, res) => { const { user } = res.locals; res.send({ user, }); }); /** * 내가 가진 장바구니 목록을 전부 불러온다. */ router.get("/goods/cart", authMiddleware, async (req, res) => { const { userId } = res.locals.user; const cart = await Cart.find({ userId, }).exec(); const goodsIds = cart.map((c) => c.goodsId); // 루프 줄이기 위해 Mapping 가능한 객체로 만든것 const goodsKeyById = await Goods.find({ where: { goodsId: goodsIds, }, }) .exec() .then((goods) => goods.reduce( (prev, g) => ({ ...prev, [g.goodsId]: g, }), {} ) ); res.send({ cart: cart.map((c) => ({ quantity: c.quantity, goods: goodsKeyById[c.goodsId], })), }); }); /** * 장바구니에 상품 담기. * 장바구니에 상품이 이미 담겨있으면 갯수만 수정한다. */ router.put("/goods/:goodsId/cart", authMiddleware, async (req, res) => { const { userId } = res.locals.user; const { goodsId } = req.params; const { quantity } = req.body; const existsCart = await Cart.findOne({ userId, goodsId, }).exec(); if (existsCart) { existsCart.quantity = quantity; await existsCart.save(); } else { const cart = new Cart({ userId, goodsId, quantity, }); await cart.save(); } // NOTE: 성공했을때 딱히 정해진 응답 값이 없다. res.send({}); }); /** * 장바구니 항목 삭제 */ router.delete("/goods/:goodsId/cart", authMiddleware, async (req, res) => { const { userId } = res.locals.user; const { goodsId } = req.params; const existsCart = await Cart.findOne({ userId, goodsId, }).exec(); // 있든 말든 신경 안쓴다. 그냥 있으면 지운다. if (existsCart) { await existsCart.delete().exec(); } // NOTE: 성공했을때 딱히 정해진 응답 값이 없다. res.send({}); }); /** * 모든 상품 가져오기 * 상품도 몇개 없는 우리에겐 페이지네이션은 사치다. * @example * /api/goods * /api/goods?category=drink * /api/goods?category=drink2 */ router.get("/goods", authMiddleware, async (req, res) => { const { category } = req.query; const goods = await Goods.find(category ? { category } : undefined) .sort("-date") .exec(); res.send({ goods }); }); /** * 상품 하나만 가져오기 */ router.get("/goods/:goodsId", authMiddleware, async (req, res) => { const { goodsId } = req.params; const goods = await Goods.findById(goodsId).exec(); if (!goods) { res.status(404).send({}); } else { res.send({ goods }); } }); app.use("/api", express.urlencoded({ extended: false }), router); app.use(express.static("assets")); app.listen(8080, () => { console.log("서버가 요청을 받을 준비가 됐어요"); });
3.gRPC 프로토콜
- gRPC란?
구글에서 최초로 개발한 오픈 소스 원결 프로시저 호출 시스템이다.
http/2를 사용해서 동작하는데 아직 웹 브라우저가 http/2를 사용할 수 없다.
해결하기 위해서 gRPC-web, gRPC <> HTTP/JSON Transcoding이 있다.
- gRPC-web
http/1 위에 올라가는 gRPC 라이브러리를 이용해서 요청을 보내고, 서버가 바로 받기 전에,
그 앞 프록시가 해당 요청을 gRPC 요청으로 변경시켜 보낸다.
- gRPC <> HTTP/JSON Transcoding
gRPC를 REST API로 변환한다.
gRPC - HTTP API 비교
기능 gRPC JSON을 사용하는 HTTP API 계약 필수( .proto) 선택 사항(OpenAPI) 프로토콜 HTTP/2 (빠름!) HTTP Payload Protobuf(소형, 이진메시지 형식) JSON(대형, 사람이 읽을 수 있음) 규범 엄격한 사양 느슨함. 모든 HTTP가 유효합니다. 스트리밍 클라이언트, 서버, 양방향 클라이언트, 서버 브라우저 지원 아니요(gRPC-웹 필요) 예 보안 전송(TLS) 전송(TLS) 클라이언트 코드 생성 예 OpenAPI + 타사 도구 .proto 파일 : gRPC서비스/ 메시지 계약 정의
출처: https://rokroks.tistory.com/49 [코딩하는 록록스]4. 4주차 1일차 소감 및 부족한 점
1. 소감
4주차 1일차에 접어들었다 3주차에 시작했던 주특기 기본을 지나 숙련 주차에 진입을 하게 됐다. 4주차라는건 내가 벌써 항해99를 시작한지 한달이 됐다는 거다... 어찌보면 시간이 빠른 것 같다. 아직 3분의 1밖에 안왔지만 그동안 나는 뭐 했나 돌아보면 대체 내가 뭘 했을까 생각을 하지만, 지금 내가 코딩하는걸 보면 확실히 실력이라던지 코드를 써내려갈때의 센스가 올라간 것 같다.
비록 아직 회사에 입사해서 코딩할 실력은 아니지만 3분의 1에서 무사히 모든 커리큘럼을 지나치고 수료를 하면 회사에 입사해서 코딩을 할 수 있는 실력이 될거라고 믿는다.
1일차에 들어와서 1주일마다 바뀌는 팀에 또 적응을 해야한다는게 불편했지만, 다 내가 하고픈대로 할 수 없으니까 그리고, 회사에 가면 더 많은 사람들을 지나치며 만날거니까 연습이라고 생각한다.
이번 심화반 강의는 3주차 기초반 강의보다 뭔가 이해하기가 쉬웠다. 저번 주에는 뭔가 어수선하고 이해가 안됐다면, 뭔가 이번 주차는 내가 기초 주차에서 어떻게 구현하지 했던 것을 구현을 해내서 그런지는 잘 모르겠지만, 뭔가 할 수 있을 것 같았다.
항상 뭔가 제출이 다가오면은 어떻게든 했던게 나였다... 진짜 나도 신기할 따름이다. 2일차를 지나서 강의를 모두 완강을 하고 들을 생각보다는 프로젝트를 병행하면서 강의를 들어보는 것도 생각해 봤다. 뭔가 쉽다고 생각할때가 실수를 잘하고 넘어지는 경우가 많아서 신중히 검토를 해봐야겠다.
2. 부족한 점
항해99 부트캠프를 진행하면서 솔직히 1주차, 2주차 때보다 긴장감이 많이 풀린 것 같다. 그 당시에는 눈에 불을 켜고 자리에서 일어나지 않고, 계속 코딩했던 적이 있던것 같은데 요즘에는 강의를 보면서 졸고 있다..
뭔가 피로가 누적이 된게 없지않아 있겠지만, 뭔가 되게 편해졌나보다. 그렇다고 해서 공부나 코딩을 게을리 하지는 않는다 하루에 평균 15시간을 하고 많게는 19시간도 한다.
하지만 이렇게 느슨해진 긴장감 때문에 실수와 의욕을 상실할 수 있다고 생각하기 때문에 졸리거나 하기 싫을 때는 스트레칭으로 머리나 눈을 좀 풀어줘야 겠다.
'항해[TIL]' 카테고리의 다른 글
[TIL] 항해 99 4주차 주특기 숙련 회고록_15일차 (0) 2021.11.28 [TIL] 항해 99 4주차 주특기 숙련 회고록_14일차 (0) 2021.11.27 [TIL] 항해 99 3주차 주특기 기본 회고록_10일차 (0) 2021.11.20 [TIL] 항해 99 3주차 주특기 기본 회고록_9일차 (0) 2021.11.20 [TIL] 항해 99 3주차 주특기 기본 회고록_8일차 (0) 2021.11.17