728x90

서버에서 두 개 이상의 응답을 보내려 하는 경우 나타나는 에러다. 

 

나는 아무리봐도 res. 응답이 중복되지 않았는데 뭐지 했더니

res.send().json 이렇게 있지도 않은 메서드로 응답했던 것~!

 

res.status(200).json() 으로 바꿔주었더니 해결되었다. 

728x90
728x90

nodejs 는 

chrome v8 javascrpit엔진으로 빌드된 javascript런타임이다.

 

( "자바스크립트 런타임"이란 "자바스크립트 코드를 실행하는 환경"을 말한다. 

자바스크립트는 일반적으로 브라우저에서 실행된다. 

다른 환경에서도 실행될 수 있지만 이를 위해서는 자바스크립트 엔진을 다른 프로그램에 내장하여 자바스크립트 코드를 실행할 수 있는 환경을 만들어야한다. 자바스크립트 엔진과 함께 필요한 라이브러리, 모듈, api 등을 포함하는 것이 자바스크립트 런타임이다. 

)

 

브라우저에서 실행되는 자바스크립트와 달리 nodejs 는 파일 시스템,네트워크 등과 같은

서버 사이드 기능을 제공하여, 서버 사이드 웹 개발을 위한 환경을 제공한다. 

이를 통해 *비동기식 I/O, 이벤트 기반 처리 모델 등을 활용해 높은 성능의 웹 애플리케이션을 만들 수 있다.

 

또한 노드js는 npm이라는 패키지 매니저를 제공한다. node package manager

이를 통해 라이브러리, 모듀르 프레임워크 등의 다양한 패키지를 쉽게 설치하고 관리할 수 있다.

 

 

(I/O(Input/Output)는 컴퓨터에서 데이터를 읽고 쓰는 작업을 말합니다. 이때, 데이터를 읽고 쓰는 과정에서 CPU는 다른 작업을 수행할 수 없는 차단(blocking) 상태에 빠지게 됩니다. 이러한 문제를 해결하기 위해 비동기식 I/O(Asynchronous I/O)가 등장했습니다.)

 

+

express는 

노드Js를 위한 웹 프레임워크이다. 간결하고 유연한 구조를 가지고 있는 것이 특징

노드js의 http 모듈을 기반으로하며, 

http 요청

라우팅 및 미들웨어 처리를 쉽게 할 수 있도록 한다.

 

다음과 같은 기능을 제공한다. 

  1. 라우팅: HTTP 요청의 URL을 해당하는 처리기(콜백 함수)에 매핑합니다. 이를 통해 사용자는 특정 URL에 대한 요청에 대한 적절한 응답을 반환할 수 있습니다.
  2. 미들웨어: 요청을 처리하기 전에 실행되는 작업을 구성할 수 있습니다. 예를 들어 요청 헤더의 유효성을 검사하거나 로깅을 수행하는 등의 작업을 수행할 수 있습니다. 또한 미들웨어를 사용하여 사용자 정의 라우팅 및 오류 처리 논리를 구현할 수도 있습니다.
  3. HTTP 요청 및 응답: Express는 HTTP 요청 및 응답을 쉽게 처리할 수 있는 기능을 제공합니다. 예를 들어 요청 본문을 구문 분석하거나 특정 상태 코드로 응답을 반환할 수 있습니다.
  4. 뷰 엔진: Express는 다양한 뷰 엔진을 지원합니다. 이를 통해 사용자는 HTML 및 CSS와 같은 마크업 언어로 페이지를 렌더링할 수 있습니다. 대표적인 뷰 엔진으로는 Pug, EJS, Handlebars 등이 있습니다.

 

728x90
728x90

axios는

지원하는 브라우저가 더 많고 

에러핸들링이 좋고

자동으로 데이터가 json형태로 되어 넘어간다

Automatic JSON data transformation

As we saw earlier, Axios automatically stringifies the data when sending requests (though you can override the default behavior and define a different transformation mechanism). When using fetch(), however, you’d have to do it manually.

Compare:

// axios
axios.get('https://api.github.com/orgs/axios')
  .then(response => {
    console.log(response.data);
  }, error => {
    console.log(error);
  });

// fetch()
fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())    // one extra step
  .then(data => {
    console.log(data) 
  })
  .catch(error => console.error(error));

fetch는 직접 json화를 해줘야하지만

지원하는 브라우저 문제 등을 충분히  해결할 수 있다.

 

 

결론

Axios는 대부분의 HTTP통신 요구사항을 위해 컴팩트 패키지로 사용하기 쉬운 API를 제공한다.

하지만 fetch()메소드를 사용해서도 Axios 라이브러리의 주요기능을 완벽하게 재현할 수 있다.

는 글

https://blog.logrocket.com/axios-vs-fetch-best-http-requests/

 

Axios vs. fetch(): Which is best for making HTTP requests? - LogRocket Blog

Axios is not always an ideal solution; depending on your needs, there are sometimes better options for making HTTP requests. The Fetch API is one of them.

blog.logrocket.com

 

728x90
728x90

https://stepby-yun.tistory.com/181

 

지난시간,,,

 

 

 

 

바디파서 해봐도 (express로 사용) 안되고 무한히 헤매었다

한번에 저장안하고 따로 할까 싶어서 마이페이지 만들어서 뺐다가 

생각해보니 해당 id에 프로필을 저장하는거라서 더 복잡해지겠다 싶었다(프로필 수정도 만들 때 그거 해야겠지만..)

어쨌든 같은 데이터 행에 넣어줘야하니까.

 

 

방법은 여러가지 있겠지만 내가 찾은 거는 

formdata.append로 아이디랑 유저도 저장하는 방법이었다.

(method) FormData.append(name: string, value: string | Blob, fileName?: string): void

append(키, 값) 

이란 거는 알았는데 multer쓰면서 

upload.single(키) 값과 같아야된대서 

 

이미지 아닌 아이디와 비번은 좀 다른 줄 알았다. (multer사용한 이미지는 req.body로 가져오는 것도 아니어서 좀 실제로 다르기도 함)

 

해당하는 키값을 

req.body.키값

으로 찍어보니 나왔다. 

 

req.body가 잘나오는게 append로 넣어줘서 그런건가..?

(바디파서 빼봐도 잘나옴)

 

 

req만 콘솔로 찍어보니 엄청 길게 나오는데 그중에 body랑 file이 보이네

 

 

 

 

 

 

으어 한번에 user정보랑 이미지경로 저장성공

 

react 프론트 코드

import React, { useState } from "react";
import Axios from "axios";
import "./signup.css";

function Signup() {
  //회원가입 값
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  // const [imageUpload, setImageUpload] = useState(null);

  const [image, setImage] = useState({
    preview:
      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAM1BMVEXk5ueutLfn6eqrsbTp6+zg4uOwtrnJzc/j5earsbW0uby4vcDQ09XGyszU19jd3+G/xMamCvwDAAAFLklEQVR4nO2d2bLbIAxAbYE3sDH//7WFbPfexG4MiCAcnWmnrzkjIRaD2jQMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMw5wQkHJczewxZh2lhNK/CBOQo1n0JIT74/H/qMV0Z7GU3aCcVPuEE1XDCtVLAhgtpme7H0s1N1U7QjO0L8F7llzGeh1hEG/8Lo7TUmmuSrOfns9xnGXpXxsONPpA/B6OqqstjC6Ax/0ujkNdYQQbKNi2k64qiiEZ+ohi35X+2YcZw/WujmslYewiAliVYrxgJYrdwUmwXsU+RdApUi83oNIE27YvrfB/ZPg8+BJETXnqh9CVzBbTQHgojgiCvtqU9thFJg/CKz3VIMKMEkIXxIWqIpIg2SkjYj+xC816mrJae2aiWGykxRNsW0UwiJghJDljYI5CD8GRiCtIsJxizYUPQ2pzItZy5pcisTRdk/a9m4amtNNfBuQkdVhSaYqfpNTSFGfb9GRIakrE2Pm+GFLaCQPqiu0OpWP+HMPQQcgQMiQprWXNmsVwIjQjYi/ZrhAqNTCgr2gu0Jnz85RSSjso0HkMFZ0YZjKkc26a/jlmh9JiDyDxi9oeorTYAzZkwwoMz19pzj9bnH/GP/+qbchjSGflneWYhtTuKdMOmNKZcJ5TjInQKcYXnESd/jQxy0ENpULTNGOGgxpap/oyw9pbUAqhfx2Dbkhovvfgz4iUzoM9+GlK6/Mh4q29hyC1mwro30hpVVLPF9wYQr71RazOeM5/cw81iBRD+A03aM9/C/obbrKjbYSpCmIVG3qT/Q8oeUo3Rz0IL7vI1tEbCB9pSiu8I/aV8x3Kg/BGWrWp4ZVs0nZfmAoEG4h/61yHYIJiFSl6Q0Vk6tTW1N8kYp8hdOkfHYYMXd2Qft+8CYwqYDSKvqIh+MCF8Wgca2u/cwdgeW3TtuVn6+1oBs3yLo5C2JpK6CvQzGpfUkz9UG/87gCsi5o2LIXolxN0FbwAsjOLEr+YJmXn7iR6N0BCt5p5cMxm7eAsfS+/CACQf4CTpKjzgkvr2cVarVTf96372yut7XLJ1sa7lv6VcfgYrWaxqr3Wlo1S6pvStr22sxOtTNPLzdY3nj20bPP+ejFdJYkLsjGLdtPBEbe/mr2bQKiXWJDroA+vtzc0p9aahuwqHMDYrQEXHEw9jwQl3drMpts9JBU1SdktPe5FBRdJQ6bwXBpa57ib2A8kukQDzMjh++Uo7Fo6Wd02Pkf4fknqoo4HtvAIjsqUcjx6DIPgWCaOML9rKI/oqD9/lgNrn+eF+p7j8tnzHBiR7+kdUGw/+V1Kzkc75mMy6U+FMaxjPibiM1U1uGM+puInHpmALZCgP4pt7i840MV8+0R1zPsRB6UTcqpizncYwZ89syDydfyWCwXB1l8/zRNGWbTG/GHKUm9AkxHMc/EGSk3z2+ArEhPEV5TUBLEvUGFcjEUH80J/jveTGOAJEljJbILWGQT3zRYiwuKsUXN1EEJAzBhRJFll7mBUG7KD8EqPkKekBREaL8hMDZLQSG6AQjtHPYmvTQnX0TtpC1SYCe2YdkkyLP3jj5BSbKiuR585eQhTgoje6yIb0Yb0C+mV6EYvebqw5SDy2WmubogZiF2AVxPC2FpDf8H2Q9QWo6IkjUxTWVEI3WY/wrCeSuqJ+eRWzXR/JXwgVjUMozbCOfoEZiSiKVGepqv5CJ8RyR4D7xBeamqa7z3BJ/z17JxuBPdv93d/a2Ki878MMAzDMAzDMAzDMAzDMF/KP09VUmxBAiI3AAAAAElFTkSuQmCC",
    data: "",
    // Headers: " 'Content-Type': 'multipart/form-data'",
  });

  //다 userlist에 담아서 db저장하기
  // const [userList, setUserList] = useState([]);

  const onChangeId = (event) => {
    setId(event.target.value);
  };

  const onChagePassword = (event) => {
    setPassword(event.target.value);
  };

  const Submit = (e) => {
    e.preventDefault();

    let formData = new FormData();
    formData.append("file", image.data);

    formData.append("iddd", id);
    formData.append("password", password);

    console.log(image.data, "선택한이미지! ");
    console.log(formData, "들어가기전폼데이터");

    Axios.post(
      "http://localhost:3001/submit",

      // {
      //   id: id,
      //   password: password,
      // },

      formData,

      {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }
    ).then(() =>
      console.log(
        formData,
        "then이후 폼데이터",
        image,
        "이건뭐",
        image.data,
        "이미지데이터"
      )
    );
  };

  const handleFileChange = (e) => {
    const img = {
      preview: URL.createObjectURL(e.target.files[0]),
      data: e.target.files[0],
    };

    setImage(img);
  };

  return (
    <div className="formBox">
      <div className="signup_id">
        <input placeholder="아이디" onChange={onChangeId}></input>
      </div>
      <div className="signup_password">
        <input placeholder="비밀번호" onChange={onChagePassword}></input>
      </div>
      <div>
        <input placeholder="비밀번호 확인"></input>
      </div>
      {image.preview && <img src={image.preview} width="100" height="100" />}
      <input
        type="file"
        onChange={handleFileChange}
        name="Images"
        accept="Images/*"
      />

      <button onClick={Submit}>제출</button>
    </div>
  );
}

export default Signup;

 

백 코드

 

const express = require("express");
const app = express();

const mysql = require("mysql");
const cors = require("cors");
const multer = require("multer");
const path = require("path");
app.use(cors());

//이거 필요한거임?
// app.use(express.static("Images"));

const db = mysql.createConnection({
  user: "*",
  host: "*",
  password: "*",
  database: "*",
});

///////

const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, "Images/");
    },
    filename: function (req, file, cb) {
      cb(null, new Date().valueOf() + path.extname(file.originalname));
    },
  }),
});

app.post("/submit", upload.single("file"), (req, res) => {
  // console.log(req.body.posts, "레큐바디");
  console.log(JSON.stringify(req.body), "JSON.stringify(req.body)");
  console.log(req.body, "바디");

  const profile = req.file.path;
  const id = req.body.iddd;
  const password = req.body.password;

  console.log(id, password, "아이디랑 패스워드");

  console.log(req.file, "파일");
  console.log(profile, "프로파일");

  console.log(profile, "프로필 들어갈 정보");

  db.query(
    "INSERT INTO userlist (userId,userPassword,userProfile) VALUES (?,?,?) ",
    [id, password, profile],

    (err, result) => {
      if (err) {
        console.log(err, "실패ㅠㅠㅠ");
      } else {
        res.send("userlist values inserted");
        // console.log("성공~");
      }
    }
  );
});

app.listen(3001, () => {
  console.log("your server is running on 3001~! yeah");
});

 

 

 

이제 

유효성검사

비밀번호 암호화 해봐야지..

 

아 그전에 데이터 삭제부터하자

 

 

 

 

https://limdef.tistory.com/m/7

728x90
728x90

지난시간,,

https://stepby-yun.tistory.com/180

 

[mySQL 이미지 저장] BLOB말고 문자열로 하는 이유 (+string문자열 VAR / CHAR 차이)

지난 시간,,, https://stepby-yun.tistory.com/179 formData 이미지데이터 보내고 받기 (react,nodejs,header) 회원가입시 아이디 비번 프로필을 등록하려했다. id password는 넘겨주고 req.body.id로 받았는데 프..

stepby-yun.tistory.com

db에 이미지 저장까지 마쳤다.

formData로 이미지를 받았다.

이미지를 불러오는걸 하기 전에

id와 password도 함께 보내는 걸 하고 싶다.

 

formData를 쓰면 req.body를 쓸 수 없고id, password는 req.body로 받아와야하니 분리해야하나 싶었는데 역시나 방법이 있었어

 

 

 참조

<form> 파일, 텍스트 </form> 다 가져오고싶은데

  • 파일업로드 미들웨어의 필수조건인 <form enctype="multipart/form-data">을 쓰면 req.body를 undefined로 받고
  • 그렇다고 req.body를 받기위해 <form enctype="multipart/form-data">를 안쓰면 express-fileuplode나 multer를 못쓴다.

 

bodyParser를 쓰면 된다고 함 (팀플할때 무지성으로 썼는데 혼자하니 이렇게 깨닫는구나 역시 왜 쓰는지 생각해봐야함)

 

 

 

파싱이란 HTML 형식으로 사용자가 제출한 데이터에 접근하는 것을 의미합니다. 'GET' 방식으로 폼을 제출하면 '쿼리 문자열'에 데이터가 추가되어 쉽게 접근할 수 있지만 'POST' 방식으로 폼을 제출하면 데이터에 접근하기가 다소 어렵습니다. 보안을 위해 인코딩되었습니다. 데이터를 매우 쉽게 파싱할 수 있는 바디 파서 NPM 방법이 있지만 이 파싱 방법을 처음부터 구현하려고 하면 몇 가지 단계가 필요합니다.

 

Parsing means accessing the data submitted by user with HTML form. When the form is submitted with the ‘GET’ method, data are appended in ‘query string’ and can access easily but when a form is submitted using the ‘POST’ method, it is somewhat difficult to get access over those data since they are encoded because of security purpose. 
There exist a body-parser NPM method that makes it very easy to parse the data but if we try to implement this parsing method from scratch, there’s involve some steps.

 

 

근데 드는 의문이

bodyParser쓰면 multer지금 쓰고 있는거 필요없는거 아닌가?

걔도 미들웨어... 둘다 쓰는건가..

 

어라라 찾아보니까

하지만, express 버전 4.16이상 부터는 'express bodyparser deprecated ( bodyParser는 더이상 지원되지 않습니다.)' 와 같은 문제가 발생한다.

그 이유는, 4.16버전 이상 부터는 express 내부에 bodyParser가 포함되기 때문이다.

라고 한다.

 

 

- app.use(express.bodyParser())
+ app.use(express.json())
+ app.use(express.urlencoded())

이렇게 바디파서 대신 밑에 두줄을 쓴다는 건데...뭔지 잘모르겠는데요

 

 

 

 

일단 코드 추가하고 실행해보니 

일단 body-parser 모듈을 사용할 때 아무 옵션을 주지 않는 다면
body-parser deprecated undefined extended: provide extended option 같은 문구가 뜬다.

 

bodyParser 미들웨어의 여러 옵션 중에 하나로 false 값일 시 node.js에 기본으로 내장된 queryString, true 값일 시 따로 설치가 필요한 npm qs 라이브러리를 사용한다.

queryString 과 qs 라이브러리 둘 다 url 쿼리 스트링을 파싱해주는 같은 맥락에 있으나 qs가 추가적인 보안이 가능한 말 그대로 extended 확장된 형태이다.
기본이 true 값이니 qs 모듈을 설치하지 않는다면 아래와 같이 false 값으로 따로 설정을 해주어야 한다.

 

오 그렇군요 감사합니다

그래도 아직 req.body는 아무것도 안들어온다

이미지만 들어오고 흠흠

 

 

 

보낼때 id랑 password추가하면 body값만 찍히고 

이미지는 안되고...

 

 

라는데 왜...안되지..

https://blog.naver.com/bunggl/221699257359

 

 

이미지 넣은 것처럼 id랑 password도 formdata에 넣어봄

 

'id,password'이렇게 한 배열로 들어가서 안됨

 

 

 

https://hyc7575.github.io/2017/05/24/2017-05-24-node-js-bodyparserAndMulter/

 

Node.js(express) - body-parser와 multer

Node.js에서 form양식을 submit을 하기위해 사용되는 body-parser와 multer 미들웨어에 대해서 간단하게 알아보려 합니다. 각 미들웨어의 용도를 짧게 소개하면 body-parser는 라우터와 미들웨어 예제때 언급

hyc7575.github.io

 

https://kirkim.github.io/javascript/2021/10/16/body_parser.html

 

[NodeJs] express.json()과 express.urlencoded()의 차이점 알아보기

1️⃣ 사용이유 (1) .json()과 .urlencoded()를 사용하지 않을 때

kirkim.github.io

 

 

 

 

 

 

 

res.json/res.send차이https://haeguri.github.io/2018/12/30/compare-response-json-send-func/

출처

res.json

https://jin2rang.tistory.com/entry/express-bodyparser-deprecated-bodyparser%EB%8A%94-%EB%8D%94%EC%9D%B4%EC%83%81-%EC%82%AC%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EC%8A%B5%EB%8B%88%EB%8B%A4-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0

 

https://velog.io/@hyunju-song/body-parser%EC%9D%98-urlencoded%EB%8A%94-%EB%8F%84%EB%8C%80%EC%B2%B4-%EC%96%B4%EB%96%A4-%EC%97%AD%ED%95%A0%EC%9D%84-%ED%95%98%EB%8A%94-%EA%B1%B8%EA%B9%8C

++

바디파서써서 req.body랑 이미지 파일 전송하기 

https://velog.io/@yuna_song/enctypemultipartform-data-%EC%82%AC%EC%9A%A9-%EC%8B%9C-req.body-%EC%82%AC%EC%9A%A9%EB%B2%95

formData 객체전송

https://melius.tistory.com/51

728x90
728x90

회원가입시

아이디 비번

프로필을 등록하려했다.

 

id password는 

넘겨주고 req.body.id로 받았는데 

프로필은 formData 객체로 묶어서 

그자체로 넘겨줬는데 자꾸 undefined 떴다. 

 

formData는 req.body가 안된다길래 

 

 

 

multer을 써봤는데도 안됨

(req.body대신 req.file 또는 files를 찍어봤지만 안됐다. )

 

formData쓸때 헤더에 타입을 넣어줘야한대서 

 

뒤늦게 

  {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }

를 추가했다. 위치는 보내는 formdata 다음에 넣어줬다.

 

이걸로도 해결이 안됐는데 신기하게 

같이보낸 id, password를 지우니 

undefined가 안뜨고 이미지가 전송돼서 images폴더에 이미지도 담겼다!

 

req.file에 이미지url이 찍혔다.

 

import React, { useState } from "react";
import Axios from "axios";
import "./signup.css";
import FormData from "form-data";

function Signup() {
  //회원가입 값
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");

  // const [imageUpload, setImageUpload] = useState(null);

  const [image, setImage] = useState({
    preview:
      "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAM1BMVEXk5ueutLfn6eqrsbTp6+zg4uOwtrnJzc/j5earsbW0uby4vcDQ09XGyszU19jd3+G/xMamCvwDAAAFLklEQVR4nO2d2bLbIAxAbYE3sDH//7WFbPfexG4MiCAcnWmnrzkjIRaD2jQMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMw5wQkHJczewxZh2lhNK/CBOQo1n0JIT74/H/qMV0Z7GU3aCcVPuEE1XDCtVLAhgtpme7H0s1N1U7QjO0L8F7llzGeh1hEG/8Lo7TUmmuSrOfns9xnGXpXxsONPpA/B6OqqstjC6Ax/0ujkNdYQQbKNi2k64qiiEZ+ohi35X+2YcZw/WujmslYewiAliVYrxgJYrdwUmwXsU+RdApUi83oNIE27YvrfB/ZPg8+BJETXnqh9CVzBbTQHgojgiCvtqU9thFJg/CKz3VIMKMEkIXxIWqIpIg2SkjYj+xC816mrJae2aiWGykxRNsW0UwiJghJDljYI5CD8GRiCtIsJxizYUPQ2pzItZy5pcisTRdk/a9m4amtNNfBuQkdVhSaYqfpNTSFGfb9GRIakrE2Pm+GFLaCQPqiu0OpWP+HMPQQcgQMiQprWXNmsVwIjQjYi/ZrhAqNTCgr2gu0Jnz85RSSjso0HkMFZ0YZjKkc26a/jlmh9JiDyDxi9oeorTYAzZkwwoMz19pzj9bnH/GP/+qbchjSGflneWYhtTuKdMOmNKZcJ5TjInQKcYXnESd/jQxy0ENpULTNGOGgxpap/oyw9pbUAqhfx2Dbkhovvfgz4iUzoM9+GlK6/Mh4q29hyC1mwro30hpVVLPF9wYQr71RazOeM5/cw81iBRD+A03aM9/C/obbrKjbYSpCmIVG3qT/Q8oeUo3Rz0IL7vI1tEbCB9pSiu8I/aV8x3Kg/BGWrWp4ZVs0nZfmAoEG4h/61yHYIJiFSl6Q0Vk6tTW1N8kYp8hdOkfHYYMXd2Qft+8CYwqYDSKvqIh+MCF8Wgca2u/cwdgeW3TtuVn6+1oBs3yLo5C2JpK6CvQzGpfUkz9UG/87gCsi5o2LIXolxN0FbwAsjOLEr+YJmXn7iR6N0BCt5p5cMxm7eAsfS+/CACQf4CTpKjzgkvr2cVarVTf96372yut7XLJ1sa7lv6VcfgYrWaxqr3Wlo1S6pvStr22sxOtTNPLzdY3nj20bPP+ejFdJYkLsjGLdtPBEbe/mr2bQKiXWJDroA+vtzc0p9aahuwqHMDYrQEXHEw9jwQl3drMpts9JBU1SdktPe5FBRdJQ6bwXBpa57ib2A8kukQDzMjh++Uo7Fo6Wd02Pkf4fknqoo4HtvAIjsqUcjx6DIPgWCaOML9rKI/oqD9/lgNrn+eF+p7j8tnzHBiR7+kdUGw/+V1Kzkc75mMy6U+FMaxjPibiM1U1uGM+puInHpmALZCgP4pt7i840MV8+0R1zPsRB6UTcqpizncYwZ89syDydfyWCwXB1l8/zRNGWbTG/GHKUm9AkxHMc/EGSk3z2+ArEhPEV5TUBLEvUGFcjEUH80J/jveTGOAJEljJbILWGQT3zRYiwuKsUXN1EEJAzBhRJFll7mBUG7KD8EqPkKekBREaL8hMDZLQSG6AQjtHPYmvTQnX0TtpC1SYCe2YdkkyLP3jj5BSbKiuR585eQhTgoje6yIb0Yb0C+mV6EYvebqw5SDy2WmubogZiF2AVxPC2FpDf8H2Q9QWo6IkjUxTWVEI3WY/wrCeSuqJ+eRWzXR/JXwgVjUMozbCOfoEZiSiKVGepqv5CJ8RyR4D7xBeamqa7z3BJ/z17JxuBPdv93d/a2Ki878MMAzDMAzDMAzDMAzDMF/KP09VUmxBAiI3AAAAAElFTkSuQmCC",
    data: "",
    // Headers: " 'Content-Type': 'multipart/form-data'",
  });

  //다 userlist에 담아서 db저장하기
  // const [userList, setUserList] = useState([]);

  const onChangeId = (event) => {
    setId(event.target.value);
  };

  const onChagePassword = (event) => {
    setPassword(event.target.value);
  };

  const Submit = (e) => {
    e.preventDefault();

    let formData = new FormData();
    formData.append("file", image.data);

    console.log(image.data, "선택한이미지! ");
    console.log(formData, "들어가기전폼데이터");

    Axios.post(
      "http://localhost:3001/submit",

      // {
      //   id: id,
      //   password: password,
      //   formData,
      //   headers: { "Content-Type": "application/x-www-form-urlencoded" },
      // }
      // {
      //   id: id,
      //   password: password,
      // },

      formData,

      {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      }
    ).then(() =>
      console.log(
        formData,
        "then이후 폼데이터",
        image,
        "이건뭐",
        image.data,
        "이미지데이터"
      )
    );
  };

  const handleFileChange = (e) => {
    const img = {
      preview: URL.createObjectURL(e.target.files[0]),
      data: e.target.files[0],
    };

    setImage(img);
  };

  return (
    <div className="formBox">
      <div className="signup_id">
        <input placeholder="아이디" onChange={onChangeId}></input>
      </div>
      <div className="signup_password">
        <input placeholder="비밀번호" onChange={onChagePassword}></input>
      </div>
      <div>
        <input placeholder="비밀번호 확인"></input>
      </div>
      {image.preview && <img src={image.preview} width="100" height="100" />}
      <input
        type="file"
        onChange={handleFileChange}
        name="Images"
        accept="Images/*"
      />

      <button onClick={Submit}>제출</button>
    </div>
  );
}

export default Signup;

 

 

 

const express = require("express");
const app = express();

const mysql = require("mysql");
const cors = require("cors");
const multer = require("multer");
const path = require("path");
app.use(cors());

app.use(express.json());
//이거 필요한거임?
app.use(express.static("Images"));

const db = mysql.createConnection({
  user: "*",
  host: "*",
  password: "*",
  database: "*",
});

const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, "Images/");
    },
    filename: function (req, file, cb) {
      cb(null, new Date().valueOf() + path.extname(file.originalname));
    },
  }),
});
// const upload = multer({
//   dest: "Images/",
//   limits: { fileSize: 5 * 1024 * 1024 },
// });

// app.post("/up", upload.array("img"), (req, res) => {
//   console.log(req.files);
// });

app.post("/submit", upload.single("file"), (req, res) => {
  console.log(req.files, "레큐파일ㄴ");
  console.log(req.body, "레큐바디");
  // console.log(req.body, "레큐바디");
  // const id = req.body.id;
  // const password = req.body.password;
  const profile = req.file;

  console.log(req.file, "파일");
  // res.json({ url: `/img/${req.file.filename}` });
  // FormData의 경우 req로 부터 데이터를 얻을수 없다.
  // upload 핸들러(multer)를 통해서 데이터를 읽을 수 있다

  //   function insertRecord(req, res) {
  // req.files.forEach((e) => {
  // // console.log(e.filename);
  // // });
  // console.log(req.file, "파일");
  // console.log(profile, "이미지");

  db.query(
    "INSERT INTO userlist (userProfile) VALUES (?) ",
    [profile],
    (err, result) => {
      if (err) {
        console.log(err);
      } else {
        res.send("userlist values inserted");
      }
    }
  );
});

app.listen(3001, () => {
  console.log("your server is running on 3001~! yeah");
});

 

문제는 id랑 password를 빼고 보내서 그런지 

 

Error: ER_WRONG_VALUE_COUNT_ON_ROW: Column count doesn't match value count at row 1

오류: ER_WRONG_VALUE_COUNT_ON_ROW: 열 개수가 행 1의 값 개수와 일치하지 않습니다.

이게 뜬다

 

아직 db저장도 안됐고 해결안된게 많지만 이미지를 받아서 행복함...

id랑 password를 같이 받으면 왜 undefined로 뜨는걸까?

따로 해야되는것인가..

 

 

 

아무튼 

header의 역할 ,context Type에 대해 공부하고

https://jw910911.tistory.com/117

formData(json으로 변환하라는 얘기도 있던데...나는 그거 안되던데..왜지?), multer

에 대해 더 공부하고 정리해봐야겠다.

+

app/router차이도 헷갈리니까 확인해놓자.

http://expressjs.com/ko/guide/routing.html

express가져와서 app으로 선언해서 썼는데 검색하다가 코드보면 express.router로 쓰는 게 무슨 차인가 싶어서..

728x90
728x90

계속 헷갈려서 애먹는..더 공부해야될 거 

  • 코드 어떤 순서로 읽혀지는지
  • 라우트

어제 오늘 해본거

1. 회원가입할 때 프로필 이미지로 넣을 사진 업로드 

사진경로가 로컬에 잘 들어가는지 확인

이제, 사진이 화면에 뜨는지 확인

 

2. 마이페이지에 이미지가 들어와져서 보이는지

 

3. 마이페이지에서 닉네임과 비밀번호 변경

 

 

일단 코드다 적어놓고 어디서 왜 막혔는지 볼까유

 

 

 

1. 회원가입할 때 프로필 이미지로 넣을 사진 업로드 

app.js 에 사용할 라우터 넣어놓고 ()

 

const signupRouter = require("./routes/login/signup");

정해놓고

 

 

app.use("/signup", signupRouter);

/이거 부를때, 이 라우터불러서 쓰겠다..

 

 

signup 라우터

//모듈
const express = require("express");
const path = require("path");
const multer = require("multer");
const fs = require("fs");
const { User } = require("../../models");
const bcrypt = require("bcrypt");
const passport = require("passport");
const { isNotLoggedIn } = require("../middlewares");

//라우터
const router = express.Router();

//회원가입 창띄우기(get)
router.get("/", (req, res, next) => {
  res.render("login/signup", { title: "회원가입" });
});

// 회원가입 데이터 입력한거 전송하는 코드
// 여기로 이동! signUp 들어왔을때 뒤에 실행
// post 데이터들 있어서(post데이터 전송)
router.post("/",isNotLoggedIn, async (req, res, next) => {
  const { email, nick, password, img } = req.body;
  //User는 데이터베이스!거기에서 email 가져옴(기존 email확인하려고! 그전 유저exUser)
  try {
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      return res.redirect("/signUp?error=exist");
    }
    const hash = await bcrypt.hash(password, 12);
    User.create({
      email,
      nick,
      password: hash,
      img: req.body.url,
    });
    //로그인 완료 메인으로~
    return res.redirect("/");
  } catch (error) {
    //여기 안에서 에러나면 catch로
    console.error(error);
    //app.js로 되돌림 얘를 호출한 데로 부르는거. return 되돌려~
    return next(error);
  }
});

// uploads 폴더
try {
  fs.readdirSync("uploads");
} catch (error) {
  console.error("uploads 폴더가 없어 폴더를 생성합니다.");
  fs.mkdirSync("uploads");
}

/* multer 기본 설정 */
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, "uploads/");
    },
    filename(req, file, cb) {
      const ext = path.extname(file.originalname);
      cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fieldSize: 5 * 1024 * 1024 },
});

/* 게시글 IMG CREATE */
router.post("/img", upload.single("img"), (req, res) => {
  console.log(req.file);

  res.json({ url: `/img/${req.file.filename}` });
});

/* 게시글 TEXT CREATE */
router.post("/", upload.none(), async (req, res, next) => {
  try {
    await User.create({
      // nick: req.body.nick,
      img: req.body.url,
    });
    res.redirect("/signup");
  } catch (error) {
    console.error(error);
    next(error);
  }
});

//모듈 전체 라우터 사용하려고
module.exports = router;

 

signup.html

{% extends "layout.html" %} {% block css %} {% endblock %} {% block content %}
<div id="join-form-body">
  <div class="join-form-title">회원가입</div>
  <!-- action에 페이지 주소입력 -->
  <form
    method="POST"
    class="join-form-container"
    id="user-id"
    onsubmit="return true"
    action="signup"
  >
    <fieldset class="join-form-field">
      <div class="input-container">
        <label for="email">Email<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="email"
          id="user-email"
          name="email"
          placeholder="Enter Your Email"
          onchange="checkMail()"
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="usereMail"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="password">Password<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="password"
          id="user-pw"
          name="password"
          placeholder="Enter Your Password"
          onchange="checkPw()"
          required
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongPw"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <input
          class="input-box"
          type="password"
          id="user-pw-confirm"
          name="reUserpw"
          placeholder="Confirm Password"
          onchange="reCheckPw()"
          required
        /><br />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongRePw"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="nick">Nickname<small style="color: red">*</small></label
        ><br />
        <input
          class="input-box"
          type="text"
          id="user-nickname"
          name="nick"
          placeholder="Enter Your Nickname"
          onchange="checkId()"
          required
        />
        <!--유효성 검사 후 잘못되었을때 경고 문자-->
        <text
          id="wrongid"
          class="wrong-message"
          style="color: red; font-size: small"
        ></text>
      </div>
      <div class="input-container">
        <label for="birthdate">Date of Birth</label><br />
        <input
          class="input-box"
          type="date"
          id="user-birth"
          name="birth"
        /><br />
      </div>
      <div class="profile-upload-content upload-img">
        <div class="img-preview">
          <img
            id="img-preview"
            src=""
            style="display: none"
            width="250"
            alt="미리보기"
          />
          <input id="img-url" type="hidden" name="url" />
        </div>
      </div>
      <div class="profile-img">
        <label class="img-btn img-file" id="img-label" for="img"
          >첨부 파일</label
        >
        <input id="img" type="file" accept="image/*" style="display: none" />
      </div>
    </fieldset>
    <input type="submit" value="Sign Up" id="signUp" onclick="checkForm()" />
  </form>
  <!-- 이미지테스트 -->
</div>
{% endblock %} {% block script %}
<script src="/login/javascripts/signupcheck.js"></script>
<script src="/login/javascripts/signup-myimg.js"></script>
{% endblock %}

//회원가입 창띄우기(get)

router.get("/", (req, res, next) => {

  res.render("login/signup", { title: "회원가입" });

});

 

이걸로 signup.html 띄워져

 

 

signup.html에서 이미지 삽입 부분

 

<div class="profile-upload-content upload-img">

        <div class="img-preview">

          <img

            id="img-preview"

            src=""

            style="display: none"  

            width="250"

            alt="미리보기"

          />

<inputid="img-url"type="hidden"name="url"/>//안보이다가 이미지 넣으면 보이게   

        </div>

      </div>

      <div class="profile-img">

        <label class="img-btn img-file" id="img-label" for="img"

          >첨부 파일</label

        >

        <input id="img" type="file" accept="image/*" style="display: none" />

//여기display:none은 파일추가버튼안보이게한거

      </div>

 

 

 

 

 input type="submit" 하면 폼 전송됨

 

 

폼 전송 받음

// 회원가입 데이터 입력한거 전송하는 코드

// 여기로 이동! signUp 들어왔을때 뒤에 실행

// post 데이터들 있어서(post데이터 전송)

router.post("/",

~~~~~~잘되면~~

 //로그인 완료 메인으로~

    return res.redirect("/");

 

multer 이미지 업로드 위해씀

 

 

 

 

 

 

/* 게시글 IMG CREATE */

router.post("/img", upload.single("img"), (req, res) => {

  console.log(req.file);

 

  res.json({ url: `/img/${req.file.filename}` });

});

 

/* 게시글 TEXT CREATE */

router.post("/", upload.none(), async (req, res, next) => {

  try {

    await User.create({

      // nick: req.body.nick,

      img: req.body.url,

    });

    res.redirect("/signup");

  } catch (error) {

    console.error(error);

    next(error);

  }

});

 

근데 이부분은 게시글 참고해서 쓴건데 병합하면서 좀 달라져서 헷갈린다..

업로드할 이미지를 경로로 부르고 User에서 찾아서 띄운다는 거같은데

밑에 코드를 게시글에서는 text띄울때썼나?싶어서

 

2. 마이페이지에 이미지가 들어와져서 보이는지

 

layout.html 에

<a href="/mypage">{{'안녕하세요! ' + user.nick + '님'}}</a>

누르면 

 

 

mypage.js

const express = require("express");
const { User, Club } = require("../../models");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});

//프로필 사진 읽기
router.get("/", async (req, res, next) => {
  try {
    const getImage = await User.findOne({
      // where: { id: req.user.id }, // 여기서 id 에러창뜨고 로그인됨
      where: { id: `${req.user.id}` }, // 문자로 바꿈. 아마도. 암튼 개선함
    });
    console.log(getImage);
    res.render("mypage/mypage", {
      title: "mountain 커뮤니티",
      signupImages: getImage,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

가져올 팔로우 정보들 넣어주고

get으로 mypage.html 띄워줘

 

mypage.html 

{% extends "layout.html" %} {% block css %} {% endblock %} {% block content %}

<div class="timeline">
  <div class="user-name">{{'안녕하세요! ' + user.nick + '님'}}</div>
  <div class="half">
    <div>팔로잉</div>
    <div class="count following-count">{{followingCount}}</div>
  </div>
  <div class="half">
    <div>팔로워</div>
    <div class="count follower-count">{{followerCount}}</div>
  </div>

  <div class="followings half">
    <h2>팔로잉 목록</h2>
    {% if user.Followings %} {% for following in user.Followings %}
    <div>{{following.nick}}</div>
    {% endfor %} {% endif %}
  </div>
  <div class="followers half">
    <h2>팔로워 목록</h2>
    {% if user.Followers %} {% for follower in user.Followers %}
    <div>{{follower.nick}}</div>
    {% endfor %} {% endif %}
  </div>
  <a id="modify"  href="/modify" class="btn">정보 수정</a>
  <body>
  <div>
    <p><div class="mypage-img">{{signupImages.content}}</div></p>
    <img style="width: 200px;" src="{{signupImages.img}}" alt="섬네일">
</div>
</body>
</div>
{% endblock %} {% block script %} {% endblock %}

 try {

    const getImage = await User.findOne({

      // where: { id: req.user.id }, // 여기서 id 에러창뜨고 로그인됨

      where: { id: `${req.user.id}` }, // 문자로 바꿈. 아마도. 암튼 개선함

    });

    console.log(getImage);

    res.render("mypage/mypage", {

      title: "mountain 커뮤니티",

      signupImages: getImage,

    });

  }

 

 

 

models에서 user.js 로

데이터베이스 설정해준거 User에서 찾아 id값을 . 그걸 getImage로 선언하고

마이페이지 띄우고

getImage를 signupImages로 다시 이름 정해주고 마이페이지에서 써먹기

 

signupImages의 이미지

 

    <p><div class="mypage-img">{{signupImages.content}}</div></p>

    <img style="width: 200px;" src="{{signupImages.img}}" alt="섬네일">

 

 

3. 마이페이지에서 닉네임과 비밀번호 변경

 

다 마찬가지로 라우트 만들고

 

  <a id="modify"  href="/modify" class="btn">정보 수정</a>

마이페이지에서 이거 누르면 라우트로 넘어가

 

 

modify.js

const express = require("express");
const passport = require("passport");
const bcrypt = require("bcrypt");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const { User } = require("../../models");

const router = express.Router();

router.get("/", (req, res, next) => {
  console.log("너니?");
  res.render("mypage/modify");
});

router.post("/", isLoggedIn, async (req, res, next) => {
  console.log("로그인됐을때수정되려나");
  const { email, nick, password } = req.body;
  try {
    if (nick && password) {
      const hash = await bcrypt.hash(password, 12);
      await User.update(
        {
          nick,
          password: hash,
        },
        {
          where: { email: email },
        }
      );
    } else if (nick) {
      await User.update(
        {
          nick,
        },
        {
          where: { email: email },
        }
      );
    } else if (password) {
      const hash = await bcrypt.hash(password, 12);
      await User.update(
        {
          password: hash,
        },
        {
          where: { email: email },
        }
      );
    }
    return res.redirect("/");
  } catch (err) {
    console.error(err);
    return next(err);
  }
});

module.exports = router;

router.get("/", (req, res, next) => {

  console.log("너니?");

  res.render("mypage/modify");

});

 

get으로

html의 마이페이지 폴더 안에 있는 modify.html 띄워줌

 

바꾼 닉넴 아이디를 

폼안에 써주고

 

 <form id="modify-form" action="modify" method="POST">

action이 경로, 여기로 보내는거 의미하는 듯함..

 

그리고 버튼 눌러 전송하면

 <button id="modify-form" type="submit" class="btn">변경</button>

 

 

 

router.post("/", isLoggedIn, async (req, res, next) => {

  console.log("로그인됐을때수정되려나");

  const { email, nick, password } = req.body;

  try {

    if (nick && password) {

      const hash = await bcrypt.hash(password, 12);

      await User.up~~~~~~

modify.js 라우터로 넘어와서 post에서 받아서 어쩌고 수정~

 

내가 에러났던게 

 

바꾸고나서 리턴을

  return res.redirect("/");

이렇게 해야되는데 /mypage/modify로 하고 있었음.. 그래서 변경버튼 눌렀을때 이 라우트 없다고 알람뜨고 돌아가보면 변경은 잘 되어있는..

 

 

 

좀 두서없이 써놓긴 했는데

 

내가 관계설정이랑 이동하는걸 아직 정확하게 이해한 게 아닌거같음

팀플하면서 맡은부분 먼저 결과물 나타나는거에 집중 중! 그리고 책은 이미 읽었지만 다 이해하지 못하는 부분도 많아서 해보면서 알아야할 거같아

점점 하면서 감잡아간다고 생각해...ㅎㅎ 콘솔찍어보면서 얘가 어디로 가는지 찾아보는중이야

 

 

 

 

//+

컴퓨터는 계속 전체코드 읽다가

사용자가 눌렀을때 거기서 멈춘대 (코드 읽는 순서 더 공부해봐야겠다. 아직 많이 헷갈려)

 

 

지금은 다른 형식으로 하지만

아까는 id값을 위에 붙여서 프로필 이미지 가져오는거 봤었음

 

 

mypage.js

 

const express = require("express");
const { User, Club } = require("../../models");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");

const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});

//프로필 사진 읽기
router.get("/:id", async (req, res, next) => {
  console.log("mypage의get"); //콘솔에 안찍힘
  try {
    const getImage = await User.findOne({
      where: { nick: `${req.params.id}` },
    });
    console.log(getImage);
    res.render("mypage/mypage", {
      title: "mountain 커뮤니티",
      signupImages: getImage,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

 

router.get("/:id"async (reqresnext=> {

  console.log("mypage의get"); //콘솔에 안찍힘

  try {

    const getImage = await User.findOne({

      where: { nick: `${req.params.id}` },

    });

    console.log(getImage);

    res.render("mypage/mypage", {

      title: "mountain 커뮤니티",

      signupImages: getImage,

    });

  } catch (error) {

    console.error(error);

    next(error);

  }

});

 

 

//콘솔에 안찍힘 -> 아까 안찍혔음 지금은 돼

안됐던 이유가 /mypage 뒤에 아이디값을 붙일거를 계속 찾고 있어서...

 

나는 mypage.js랑

mypage.html만 보고 있었음

 

 

 

 

근데 마이페이지로 들어가는

메인 layout.html을 봤어야했어..

 

<a href="/mypage/{{user.nick}}"
              >{{'안녕하세요! ' + user.nick + '님'}}</a
            >

 

전에는

a href="/mypage"

 

 

mypage에 id값을 붙이고 mypage.html 부르려고하니까 안됐어

 

아예 mypage 페이지 안불러와짐

 

a href="/mypage/{{user.nick}}"

이렇게 해주니까 

라우트에서 아이디값붙이고 콘솔찍히고

User에서 nick 찾고 

mypage.html 렌더해줌

 

 

 

 

////근데 이렇게 하면 주소창에

/mypage/User.nick 이 찍히니까

개선한 지금이 나아~

 

 

 

 

이해하려고 이것도 읽어봤음..

https://fierycoding.tistory.com/20

 

 

 

728x90
728x90

https://velog.io/@pkbird/Nunjucks-basic

 

팔로우하기

팔로우끊기

 

버튼교체!!!!

 

해결함 

넌적스 제대로 안됐던 이유는 club.html코드보면서 라우터 mypage.js 걸로 왜 안되냐 하고 있었던 것~~

 

 (mypage에서 팔로우 정보 불러올때 썼던 것처럼)

 

club.html에서 쓸

followerIdList를 club.js에서 쓸거라고 넣어줘야 됐음

 


목표:

(나자신)   - 아무것도 안뜨게

(팔로잉한 사람followerIdlist에 포함되어있음)  - 팔로우끊기

(팔로우 안함사람) – 팔로우하기

 

///넌적스 if절은//

만약에 트윗유저가 followerIdlist에 포함되어있지 않고 나자신이 아니면

팔로우하기

그게 아니면(트윗유저가 followerIdlist에 포함되어있으면 그리고 나자신 아니면)

팔로우끊기

 


 

 

 

 

(좋아요는 아직 기능안해서 주석처리)

 

club.html

{% extends "layout.html" %} {% block css %}
<link rel="stylesheet" href="/club/stylesheets/club.css" />
{% endblock %} {% block content %}
<div class="search_field">
  <div class="fake_field">
    <input
      class="picture-search"
      type="text"
      name="search"
      placeholder="사진 검색"
    />
    <button class="picture-btn btn">검색</button>
  </div>
</div>

<div class="feed-container">
  <h3>인기 피드</h3>

  <!-- <form id="myFeed">
    <button id="feed-btn" type="submit" class="black btn">
      <a class="picture-uploads" href="/clubupload">업로드</a>
    </button>
    {% for twit in twits %}
    <tr class="clubMain">
      <td>
        <a href="/">
          <img style="width: 100px" src="{{twit.img}}" alt="섬네일"/>
        </a>
      </td>
    </tr>
    {% endfor %}
  </form> -->
  {% if user and user.id %}
  <button id="feed-btn" type="submit" class="black btn">
    <a class="picture-uploads" href="/clubupload">업로드</a>
  </button>
  {% endif %} {% for twit in twits %}
  <div class="twit">
    <input type="hidden" value="{{twit.User.id}}" class="twit-user-id" />
    <input type="hidden" value="{{twit.id}}" class="twit-id" />
    <div class="twit-author">{{twit.User.nick}}</div>

    {% if not followerIdList.includes(twit.User.id) and twit.User.id !== user.id
    %}
    <button class="twit-follow btn">팔로우하기</button>
    {% elif twit.User.id !== user.id%}
    <button class="twit-unfollow btn">팔로우끊기</button>
    {% endif %}

    <div class="twit-img">
      <a href="/clubdetail/:id"
        ><img style="width: 100px" src="{{twit.img}}" alt="섬네일"
      /></a>
    </div>

    <button class="like btn">좋아요</button>

    <button class="unlike btn">좋아요 취소</button>

    <div class="twit-content"></div>
  </div>
  {% endfor %}
</div>
{% endblock %} {% block script %}
<script>
  //팔로우
  document.querySelectorAll(".twit-follow").forEach(function (tag) {
    console.log("팔로이있는창");
    tag.addEventListener("click", function () {
      console.log("팔로우클릭");
      const myId = document.querySelector("#my-id");
      if (myId) {
        console.log("myid가져와지니?");
        const userId = tag.parentNode.querySelector(".twit-user-id").value;
        console.log(userId);
        //if (userId !== myId.value)원래 이거였음 근데 바꿔도 콘솔에는 찍히네

        if (userId !== myId.value) {
          if (confirm("팔로잉하시겠습니까?")) {
            console.log("팔로요청되나?");
            axios
              .post(`/user/${userId}/follow`)
              .then(() => {
                console.log("팔로됐나");
                location.reload();
              })
              .catch((err) => {
                console.log("에러");
                console.error(nperr);
              });
          }
        }
      }
    });
  });
  //팔로우끊기
  document.querySelectorAll(".twit-unfollow").forEach(function (tag) {
    tag.addEventListener("click", function () {
      const myId = document.querySelector("#my-id");
      console.log(myId);
      if (myId) {
        const userId = tag.parentNode.querySelector(".twit-user-id").value;

        if (userId !== myId.value) {
          if (confirm("팔로잉끊으시겠습니까?")) {
            console.log("팔로요청되나?");
            axios
              .post(`/user/${userId}/unfollow`)
              .then(() => {
                console.log("팔로됐나");
                location.reload();
              })
              .catch((err) => {
                console.log("에러");
                console.error(err);
              });
          }
        }
      }
    });
  });

  //   forEach.call(document.querySelectorAll(".like"), function (tag) {
  //   tag.addEventListener("click", function () {
  //     var isLoggedIn = document.querySelector("#my-id");
  //     var twitId = tag.parentNode.querySelector("#twit-id").value;
  //     if (idLoggedIn) {
  //       var xhr = new XMLHttpRequest();
  //       xhr.onload = function () {
  //         if (xhr.status === 200) {
  //           location.reload();
  //         } else {
  //           console.error(xhr.responseText);
  //         }
  //       };
  //       xhr.open("POST", "/club/" + twitId + "/like");
  //       xhr.send();
  //     }
  //   });
  // });
  // forEach.call(document.querySelectorAll(".unlike"), function (tag) {
  //   tag.addEventListener("click", function () {
  //     var isLoggedIn = document.querySelector("#my-id");
  //     var twitId = tag.parentNode.querySelector("#twit-id").value;
  //     if (idLoggedIn) {
  //       var xhr = new XMLHttpRequest();
  //       xhr.onload = function () {
  //         if (xhr.status === 200) {
  //           location.reload();
  //         } else {
  //           console.error(xhr.responseText);
  //         }
  //       };
  //       xhr.open("DELETE", "/club/" + twitId + "/like");
  //       xhr.send();
  //     }
  //   });
  // });
</script>

{% endblock %}

 location.reload(); //이거 새로고침!

<button class="twit-unfollow btn">팔로우끊기</button//띄어쓰기하면 클래스 하나 더 들어가는거

 

 

팔로잉한 데이터 가져오려면

라우터에서 만들어줘야했음!

 

 

경로는 라우터에서

get으로 club.html불러주고

use로 사용하겠다.

어떻게 사용할지~

 

club.js

const express = require("express");
const { isLoggedIn, isNotLoggedIn } = require("../middlewares");
const { Club, User } = require("../../models");
const router = express.Router();

router.use((req, res, next) => {
  res.locals.user = req.user;
  // res.locals.followerCount = 0;
  // res.locals.followingCount = 0;
  // res.locals.followerIdList = [];
  res.locals.followerCount = req.user ? req.user.Followers.length : 0;
  res.locals.followingCount = req.user ? req.user.Followings.length : 0;
  res.locals.followerIdList = req.user
    ? req.user.Followings.map((f) => f.id)
    : [];
  next();
});


router.get("/", async (req, res, next) => {
  try {
    const clubs = await Club.findAll({
      include: {
        model: User,
        attribute: ["id", "nick"],
      },
      order: [["createdAt", "DESC"]],
    });
    res.render("club/club", {
      title: "mountain feed",
      twits: clubs,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

참고로 라우트 쓸려면

 

app.js

에 라우트 선언해야함

const clubRouter = require("./routes/club/club");

미들웨어도 추가

app.use("/club"clubRouter);

 

 

 

 

결과물~!!

마이페이지~

 

 

 

 

 

https://design-system.service.gov.uk/components/tabs/

 

Tabs – GOV.UK Design System

Components Tabs Experimental This component is currently experimental because more research is needed to validate it. The tabs component lets users navigate between related sections of content, displaying one section at a time. Contents Past day Past week

design-system.service.gov.uk

이건 넌적스 유용하게 쓸 수 있을 거 같은 사이트

728x90
728x90

깔고 

 

콘솔에서 

cd "경로"

cd 경로 로 해도 되더라

 

 

!!!

C:\data\db

폴더 없으면 실행안돼! 반드시 폴더 먼저 만들어야함

 

 

mongod

 

기본적으로 27017번 포트에서 실행됨

 

몽고디비 사용할때마다 mongod 명령어로 먼저 서버를 실행해야 함

 

(매번 실행하기 귀찮으면 명령 프롬프트를 관리자 권한으로 실행한 뒤 명령어 입력하는 방법도 있음 책353쪽 참고.)

 

 

몽고디비 프롬프트 접속하려면 같은 폴더에서 콘솔 하나 더 열어 mongo 명령어 입력

mongo

 

 

프롬프트가 >로 바뀌면 성공

지금은 누구나 몽고디비에 접속할 수 있으니 

관리자 계정을 추가하자

 

> use admin
switched to db admin
> db.createUser({user:'이름',pwd:'비밀번호',roles:['root']})  나 오타진짜 많이 낸다...안돼서 꽤 찾음
Successfully added user: { "user" : "이름", "roles" : [ "root" ] }

 

db.createUser 메서드로 계정 생성!

user에 사용자 이름 넣고 pwd 자리에 사용할 비번 입력 roles로는 현재 모든 권한이 있는root를 부여

 

아까 mongod를 입력했던 콘솔을 종료한 뒤

이번에는 mongod --auth명령어로 접속 (--auth는 로그인이 필요하다는 뜻)

 

 

 

다른창 끄고

여기서도 

exit

로 나와주고

 

mongod --auth 명령어로 접속 --auth는 로그인이 필요하다는 뜻

 

 

접속할 때 

앞으로 

cd 경로

 

admin -u 이름 -p 비번

하고

 

콘솔에 데이터베이스 만들거면  use 데이터베이스명

데이터베이스목록을 확인하는 명령어 show dbs

 

 

 

 

 

 

 

728x90
728x90

설치 완료 후

 

 

mysql로 접속

 

설치된 폴더 경로로 들어가서 

cd 폴더경로

 

 

들어가서> mysql -u root -p

 

비번 입력하면 뜸!

 

나가고 싶으면

exit

 

데이터베이스 생성

create database movie_db default character set utf8;

 

영화정보 넣을거라서  movie_db로 넣었음

 

create database movie_db default character set utf8;

(MySQL이 기본적으로 알고 있는 구문을 예약어라고 부름 예약어는 소문자로 써도 되지만 대문자로 쓰는게 좋아

사용자가 직접만든 이름이랑 구분하기 위해서)

 

 

이거 사용할거야~

mysql> USE movie_db;

 

 

이전에 만든

데이트베이스 리스트 보고 싶으면

mysql> show databases;

 

그중에서 사용할 꺼 USE 데이터베이스

하고 

테이블 보기

mysql> show tables;

 

 

 

 

 

 



 

 

 

테이블(데이터가 들어갈 수 있는 틀) 생성하는 명령어

 

create table [데이터베이스명. 테이블명] 

 

mysql>  CREATE TABLE products(
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY);

 

 

아까 USE 테이터베이스명 해서 

생략해도 됨

 

 

만들어진 테이블 확인하는 명령어 

DESC 테이블명

 

 

 

테이블  컬럼 추가

ALTER TABLE `테이블명` ADD `컬럼명` VARCHAR(50) NOT NULL;

 

mysql> ALTER TABLE `products` ADD `name` VARCHAR(50) NOT NULL;

 

악 에러 뜬 이유가 `` 뱁틱이어서 그랬음..ㅎㅎ

 

 

이렇게 괄호로 묶어서 한번에 여러 개 넣어줄 수도 있음

 

 

 

 

개념 정리하고 가자~~~

 

(아직 데이터를 안넣어서 책에 나온 예시로 정리)

 

컬럼과 로우

id  name  age married
1 zero 24 false
2 nero 32 true
3 hero 28 false

 

컬럼(column) : id, name, age,married 과 같은 세로줄 

(age 컬럼에는 24,32,28과 같은 정보 담겨있음)

로우(row) : 1,zero,24,false와 같은 가로줄. 

 

필드 : 컬럼과 로우가 교차하는 칸 하나

컬럼은 세로필드의 집합

로우는 가로 필드의 집합

테이블에 데이터를 넣을 때는 미리 컬럼을 정의해두고 컬럼에 맞춰 데이터를 넣으면 된다.

 

컬럼의 자료형

  • INT : 정수 의미. 소수까지 저장하고 싶다면 FLOAT 나 DOUBLE 자료형 사용
  • VARCHAR(자릿수) : 가변길이. 0~자릿수인 문자열 넣을 수 있음
  • CHAR(자릿수) : 고정길이 자료형. 부족한 자릿수만큼 스페이스가 채워짐
  • TEXT : 긴 글 저장할 때 사용. 수백 자 이내의 문자열은 보통 VARCHAR(자릿수)사용하고 그보다 길면 TEXT 사용
  • TINYINT : -128부터 127까지의 정수 저장할 때 사용. 1또는 0만 저장하면 불값(Boolean)과 같은 역할 할 수 있음
  • DATETIME : 날짜와 시간에 대한 정보 담고 있음. 날짜 정보만 담는 DATE / 시간정보 담는 TIME 자료형

 

(자료형 뒤) 옵션

  • NULL 과  NOT NULL  : 빈칸 허용할지 여부 묻는 옵션
  • AUTO_INCREMENT : 숫자를 저절로 올리겠다는 뜻 (데이터 넣으면 알아서 id로 1번부여, 다음 2번 부여 이렇게)
  • UNSIGNED : 숫자 자료형에 적용되는 옵션. 음수 무시되고 0~4294967295까지 저장가능.나이 컬럼에 체크해두는게 좋음
  • ZEROFILL : 숫자의 자릿수가 고정되어 있을 때 사용할 수 있음(예) INT(4)인데 숫자1넣으면 0001이 되는 식)
  • created_at 에는 DEFAULT now()라는 옵션 붙어있음. 현재 시각 넣으라는 뜻. now()대신 CURRENT_TIMESTAMP를 적어도 같은 뜻이 됨. 사용자 정보를 넣으면 created_at 컬럼에는 넣는 순간의 시각이 자동으로 기록됨.
  • 해당 컬럼이 기본 키인 경우에 PRIMARY KEY옵션을 설정. 기본 키란 로우를 대표하는 고유한 값을 의미

 

DEFAULT CHARACTER SET 을 utf8로 설정하지 않으면 한글이 입력되지 않으니 반드시 설정해야 함

 

 

 

테이블 잘못 만들었을 경우 제거하는 법

DROP TABLE [테이블명] ;

 

데이터 입력

INSERT INTO 테이블명 (필드,필드) VALUE ('테이터','데이터'..)

 

선택

Select * from products

전체 선택

 

select 필드, 필드 from 테이블 where 조건

(조건검사 : where 데이터 베이스 안에서 원하는 데이터를 조건에 맞게)

 

 

 

 

 

 

 

 

기본적인 mySQL 관계(비교)연산자 : 주어진 좌우 값을 비교하는 연산자
좌변과 우변이 같다
!= 또는 <>     좌변과 우변이 다르다
<   작다
<=   작거나 같다
크다
>= 크거나 같다

 

 

 

예시

 

 

논리연산자 : 참(true), 거짓(false) 두 가지를 가지고 비교하는 연산자
AND , && 비교하는 값 모두 참(true)여야 결과가 참(true)
OR , ||  비교하는 값 중 하나라도 참(true)이면 결과가 (true) 

예시

 

 

만약 글자에서 특정 부분 일치하는 데이터를 조회하고 싶다면!

LIKE

%는 다른 경우 들어갈 자리에 붙여줌

 

 

정렬하기 

order by

전체 선택해서 

openDate 기준으로 오름차순

 

오름차순 asc (안적어도 자동이긴 함)

내림차순 desc

 

 

특정 위치에 있는 데이터를 선택

LIMIT

상위부터 2까지

 

 

 

 

 

데이터를 3개를 건너뛴 다음에 데이터 1개

 

 

명령어 순서 지켜야함!

 

 

데이터를 수정할 때

update라는 명령어 써주면 됨

여러개 수정할 때

콤마, 사용

 

 

데이터 삭제

테이블 삭제 

DROP TABLE 테이블명;

데이터베이스 삭제

DROP TABLE 데이터베이스 이름;

728x90
728x90

앞에 올린 글

노드js express로 로그인 창만들때 

헷갈렸던 거

 

 

app.get   app.post 차이

 

 

그니까(내가 본 설명글 / 이것도 있음)

get은 /주소 보이는 방식이라서 /뒤에 주소 써주는데로 이동하고 띄우고 get써서 경로이동할 수 있게 하고
post는 주소 안보여주는 방식. 눈에 보이지 않는 주소요청 받아처리함. post에서는 서버에서 처리할 일 요청할 수도 있고 데이터를 전송할 수도 있음

 

앞에 쓴 글

코드 보면

html에서

post 로 form 써서 

메인페이지랑

로그인페이지 둘다 name 데이터 전송해서 썼어

 

app.js 에서는 경로 이동 쉽게 할려고 get쓰고

post로 정보 받아서 썼구나

오호...

 

res.send 특징 

res.write 써야돼? 여러개 쓰고 싶으면?

 

https://fierycoding.tistory.com/18

 

  • res.send(body), res.send(status, body) : 클라이언트에 응답을 보냄. 상태 코드는 옵션. 기본 콘텐츠 타입은 text/html이므로 text/plain을 보내려면 res.set(‘Content-Type’, ‘text/plain’)을 먼저 호출 해야한다. JSON을 보낼거면 res.json을 쓰자.

여기 req res 메서드 개념 잘 정리 되어있다!!

 

사실 정말 가려운 곳 못긁었는데 차차 해보면서 알아가도록 ..

 

 

키 값

name vaule 차이가 뭐야

넘길 때 왜 name 넘겨주면 value는 왜 쓰지..  

 

 

 

 

Value = The value attribute specifies the value of an element.

Name = name is only to post form data. The name definies what the name of the attribute will be as soon as the form is submitted. So if you want to read this attribute later you will find it under the "name" in the POST or GET Request. Whereas the id is used to adress a field or element in javascript or css.

 설명굿

 

아하!!

맞다

 

이분 오타내심 value

요거네 name이 전달되고 value는 입력 태그의 초기값

 

HTML - input태그와 그 속성 type, value, name - 입력태그 (1)

HTML - input태그와 그 속성 type, value, name 입력태그 (1)  오늘은 input태그와 그 속성 type, value, name에 대해서 알아 보도록 하겠습니다. 태그 기초부터 알아보기 전에 압타나 스튜디오를 직접 설치 하.

yangbari.tistory.com

오호라 헷갈렸어

728x90
728x90

만들기에 앞서서 

이거 먼저 만들어놔야

어떤 모듈 들어가있는지 확인할 수 있어

 

만드는 법  npm init

 

수업 듣다가 놓쳐서 에러뜨길래 물어보니까

 

 

 

스크립트에 

"start" : "nodemon app"

으로 바꿔줘야 했고

 

scripts 부분에 start 속성 잊지 말고 넣어야~!!

nodemon app을 하면 app.js를 nodemon으로 실행한다는 뜻이야  

 

코드 수정하면 노드몬이 서버 자동으로 재시작함 콘솔에서 rs 입력해서 수동으로 재시작할 수 있어

(개발용으로만 하는거 권장)

 

 

 

익스프레스 쓸거니까 밑에 애들깔아줘야 저렇게 들어가는 거임

 

npm i express

npm i -D nodemon

npm i morgan cookie-parser express-session dotenv body-parser

 

한번에 여러 개 깔 수 있음 (밑줄친건 안깔았나? 보면 없어서 안깔은거같기도 하고..)

 

원래 로그인페이지만 따라 만들고

과제로 다른 페이지로 넘어가는 거 함. 

 

일단 결과물

 

 

 

 

로그인 페이지 login.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Login Page</h1>
    <hr />
    <form method="post">
      <table>
        <tr>
          <td><label>UserName</label></td>
          <td><input type="text" name="login" /></td>
        </tr>
        <tr>
          <td><label>Password</label></td>
          <td><input type="password" name="password" /></td>
        </tr>
      </table>
      <input type="submit" name="" />
    </form>
  </body>
</html>

 

 

메인 페이지 main.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>메인페이지</title>
  </head>
  <body>
    <form method="POST">
      <h1>메인페이지</h1>
      <button name="loginpage" value="login-page">로그인페이지</button>
      <button name="naver" value="naver-page">검색페이지</button>
    </form>
  </body>
</html>

 

 

app.js

const express = require("express");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const fs = require("fs");
const app = express();
const port = 3001;

//미들 웨어 설정
app.use(morgan("dev"));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));

//라우터 설정
// app.get("/", (req, res) => res.send("Hello World"));
//들어가서 뜨는 로그인페이지 만약 쿠키들어있으면 성공 뜸
// app.get("/", (req, res) => {
//   if (req.cookies.auth) {
//     res.send("<h1>Login Success</h1>");
//   } else {
//     res.redirect("/login");
//   }
// });
// //메인페이지 추가해봄
app.get("/", (req, res) => {
  if (req.cookies.auth) {
    res.send("<h1>Login Success</h1>");
  }

  // fs.readFile("main.html", (error, data) => {
  //   res.send(data.toString());
  // });
  else {
    // res.redirect("/login");

    //send는 한번만 쓸수 있어서..end처럼끝내버리는거였음 그래서 밑에또부를때충돌남!!
    //여러개쓰고 싶으면 write
    //그거뿐만 아니라 불러올때 충돌나는것도 있음..
    fs.readFile("main.html", (error, data) => {
      res.send(data.toString());
    });
  }
});

app.post("/", (req, res) => {
  if (req.body.loginpage) {
    console.log(req.body);
    res.redirect("/login");
  } else req.body.naver;
  {
    res.redirect("https://www.naver.com/");
  }
});

//html페이지로
app.get("/login", (req, res) => {
  fs.readFile("login.html", (error, data) => {
    res.send(data.toString());
  });
});
//단순페이지요청(클라이언트가ㅏ요청한걸로 쿠키생성)
app.post("/login", (req, res) => {
  //쿠키생성
  let login = req.body.login;
  let password = req.body.password;
  console.log(login, password);
  console.log(req.body);
  if (login == "rint" && password == "1234") {
    res.cookie("auth", true);
    res.redirect("/");
  } else {
    res.redirect("/login");
  }
});

app.listen(port, () => console.log("example app listening on port"));

 

 

 

콘솔에서

cd 폴더주소

npm start

 

하면 

콘솔 넣은거 example app listening on port 잘 뜸

 

port 값 3001로 할당해놔서 들어가면

뜬다! 

 

버튼 두개 만들어놓은 이유가 button 눌렀을 때 name 값 잘 보내와지는지 볼라고

메인페이지 일때 

 

만약

localhost:3001/ 일때

요청.바디에 name=loginpage 이면

응답. 다시지시. localhost:3001/login

 

그것도 아니면 

요청.바디에.naver이면

응답. 다시지시.링크

 

 

로그인 페이지 넘어오면 

 

내가 get post 차이가 뭘까 에러날때마다 

다같이 얘기했음 이것도 찾아서 정리해봐야겠다

 

암튼 내가 이해한거는  

/login 일때

fs.readFile 이거는 파일 읽어서 불러오고 띄우고 

응답으로 res.send (express에서는 이거 사용)

 

 

요청.바디에.login을 login에 할당

할당하고

만약 input의 name=login이랑 정한 값이랑 같으면

응답.특정 쿠키를 만들어서

응답. "/"로 다시 불러와

 

그거 아니면

응답."/login" 불러와

 

 

 

설정한 값 잘 넣어서

쿠키값이 가지고 응답되었어!

그래서 "/" 다시 메인페이지로 돌아왔는데

왜 글자가 뜨냐면

 

 

 

 

 

 

 

만약에 

요청.쿠키들이.auth 면

응답.글자로~

 

이거 넣어서 임.

 

사실 처음에 잘되다가 

[nodemon] app crashed - waiting for file changes before starting...

 

다시 메인페이지, 버튼 두개 있는 창 띄우고 싶어서쿠키값을 오른쪽에서 삭제했는데에러가 막뜨는거야 

 

 

컨트롤 C해서

npm start

 

다시 구동했는데 됐다가 안됐다가...에러 자꾸 뜸

 

 

 

redirect를 많이해서 그런가 했는데 

위에 코드에도 적었지만

 

 

res.send 때문이었음

이게 응답과 동시에 끝내줘서 다음 동작에 안넘어가는 거야

 

그리고 원래 

 

  fs.readFile("main.html", (errordata=> {

      res.send(data.toString());

    });

 

이게 else안에 있는게 아니라 "/" 일때 맨 위에 있었고 그다음에 if였음

 

메인페이지 띄우라는 거랑

쿠키값 있을때 h1 글자창 띄우라는 거랑 충돌 났나봐

 

그래서 else 안에 넣어준거임

res.send도 하나만 쓸 수 있어서 if else 안에 넣어주고...

 

 

 

app.get

app.post 차이

 

 

res.send res.write 차이 궁금~

728x90
728x90

책 Node.js 교과서 개정2판 조현영 지음

const express = require("express");
//express모듈을 app변수에 할당
const app = express();
//app.set("port", 포트);로 서버가 실행될 포트를 설정
//process.env 객체에 PORT속성이 있으면 그값을 쓰고 아니면 3000
//app.set(키,값)
app.set("port", process.env.PORT || 3000);
//app.get(주소,라우터)는 주소에 대한 get요청 올때 어떤 동작할지 적는부분
//현재GET /요청시 응답으로 Hello,Express를 전송.
//익스프레스에서는 res.write나 res.end대신 res.send를 사용하면 됨
app.get("/", (req, res) => {
  res.send("Hello,Express");
});
//(GET요청 외에도 POST,PUT,PATCH,DELETE,OPTIONS에 대한 라우터를 위한 app.post,app.put~~메서드가 존재한다)
//listen은 http웹서버와 동일.포트연결하고 서버실행. 포트는 app.get('port')로 가져옴
app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

///단순 문자열 대신 HTML로 응답하고 싶다면 res.sendFile메서드 사용하면 됨
파일의 경로를 path모듈을 사용해서 지정해야함
const express = require("express");
const path = require("path");
const app = express();
app.set("port", process.env.PORT || 3000);

app.get("/", (req, res) => {
  //res.send('Hello,Express');
  res.sendFile(path.join(__dirname, "/index.html"));
});

app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

 

 

 

미들웨어도 해보기

////6.2 자주 사용하는 미들middle웨어
//미들웨어는 익스프레스의 핵심
//요청과 응답의 중간(미들middle)에 위치하여 미들웨어라 부름
//라우터와 에러 핸들러 또한 미들웨어의 일종
//미들웨어는 요청과 응답을 조작하여 기능을 추가하긷 하고 나쁜 요청을 걸러내기도함
//미들웨어는 app.use와함께 사용. >>>app.use(미들웨어)<<

//익스프레스 서버에 미들웨어를 연결해보자
const express = require("express");
//
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const dotenv = require("dotenv");
const path = require("path");
//
dotenv.config();
const app = express();
app.set("port", process.env.PORT || 3000);
//app.use에 매개변수가 req,res,next인 함수를 넣으면 됨
//미들웨어는 위에서 아래로 순서대로 실행되고 요청과 응답사이에 특별한 기능추가할수있음
//next라는 세번째 매개변수를 사용했는데 다음 미들웨어로 넘어가는 함수임
app.use(morgan("dev"));
app.use("/", express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env.COOKIE_SECRET,
    cookie: {
      httpOnly: true,
      secure: false,
    },
    name: "session-cookie",
  })
);

//

app.use((req, res, next) => {
  console.log("모든 요청에 다 실행됩니다.");
  next();
});
//현재app.get 라우터에 미들워에 두개 연결되어있음 이때도 next호출해야 다음 미들웨어로 넘어감
app.get(
  "/",
  (req, res, next) => {
    console.log("GET / 요청에서만 실행됩니다.");
    next();
  },
  (req, res) => {
    throw new Error("에러는 에러 처리 미들웨어로 갑니다.");
  }
);
//에러처리미들웨어는 매개변수가 err, req, res, next로 네개야
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send(err.message);
});

app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

(솔직히 아직 이해는 잘 안되는데 설명 적어놓기)

 

dotenv 패캐지는 .env 파일을 읽어서 process.env로 만든다.

dotenv 패키지의 이름이 dot(점)+env인 이유다. process.env.COOKIE_SECRET에 cookiesecret 값이 할당된다.

키=값  형식으로 추가하면 된다. process.env를 별도의 파일로 관리하는 이유는 보안과 설정의 편의성 때문.

비밀 키들을 소스 코드에 그대로 적어두면 소스코드 유출 시 키도 유출됨. 따라서  .env 같은 별도의 파일에 비밀 키를 적어두고 dotenv 패키지로 비밀키를 로딩하는 방식으로 관리하곤함. .env 파일만 잘 관리하면 비밀키 지킬 수 있다.

 

 

터미널 이렇게 잘 뜸

(책에서 콘솔 이라는데 )

 

얘는 morgan 미들웨어에서 나오는 것. 

[HTTP 메서드] [주소] [HTTP 상태코드] [응답속도]-[응답 바이트]

 

를 의미함. 요청과 응답 한눈에 볼 수 있음

 

 

 

 

 

 

morgan 미들웨어 다음과같이 사용

app.use(morgan("dev"));

dev 이외에 combined, common, short, tiny 등을 넣을 수 있음 

인수 바꾸면 로그 달라져~

 

 

static 미들웨어

정적인 파일들을 제공하는 라우터(클라이언트의 요청 경로(path)를 보고 이 요청을 처리할 수 있는 곳으로 기능을 전달해주는) 역할을 함.

다음과 같이 사용

app.use("요청경로"express.static("실제경로"));

우리가 쓴거~

app.use("/"express.static(path.join(__dirname"public")));

 

함수의 니수로 정적 파일들이 담겨있는 폴더를 지정하면 된다. p237 이해안되면 책봐 미래의 나. 

예로

특정폴더명/stylesheets/style.css 는

http://localhost:3000/stylesheets/style.css  로 접근할 수 있음

css, js,이미지 파일들을  특정폴더에 넣으면 브라우저에서 접근할 수 있게 됨~

 

서버의 폴더 경로와 요청 경로가 달라서 외부인이 서버구조 쉽게 파악할 수 없어 보안에 좋아~

 

정적 파일들을 알아서 제공해줘서 4.3절(p190쿠키와 세션이해하기)처럼 fs.readFile로 파일 직접 읽어서 전송할 

필요 없음. 만약 요청 경로에 해당하는 파일이 없으면 알아서 내부적으로 next 포출함.

만약 파일을 발견했다면 다음 미들웨어는 실행되지 않음. 응답으로 파일 보내고 next를 호출하지 않으니까.

 

 

body-parser

요청의 본문에 있는 데이터를 해석해서 req.body객체로 만들어주는 미들웨어.

보통 폼 데이터나 AJAX 요청의 데이터를 처리함

단 멀티파트(이미지,동영상,파일) 데이터는 처리하지 못해서 뒤에 나오는 multer 모듈 사용하면 됨

 

 

body-parser 미들웨어 이렇게 씀

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

 

body-parser는 JSON과 URL-encoded 형식의 데이터 외에도

Raw(요청의 본문이 버퍼 데이터일때 해석하는 미들웨어),

Text(덱스트 데이터 일때 해석하는 미들웨어)

형식의 데이터를 추가로 해석할 수 있음

 

 

 

npm i body-parser

 

이거 적어서 깔고 아래꺼 추가함

//body-parser
const bodyParser = require("body-parser");
app.use(bodyParser.raw());
app.use(bodyParser.text());

JSON은 JSON 형식의 데이터 전달 방식이고 URL-encoded 는 주소형식으로 데이터를 보내는 방식.

폼 전송은 주로 후자 사용함. 

urlencoded 메서드 보면 { extended: false } 라는 옵션이 들어있음 

이 옵션이 false면  노드의 querystring 모듈을 사용해 쿼리 스트링을 해석하고 true면 qs 모듈을 사용해서 쿼리스트링을 해석한다........

4.2절에서 POST와 PUT 요청의 본문 전달 받으려면 req.on('data') 와  res.on('end') 로 스트림 사용해야 했지

하지만 body-parser을 사용하면 그럴 필요없어 내부적으로 스트림을 처리해 req.body에 추가한다. 

 

 

cookie-parser

요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만든다.

 

사용하는 법

app.use(cookieParser(비밀키)); 

해석된 쿠키들은 req.cookies 객체에 들어감예로 name=zerocho 쿠키를 보내면req.cookies는 {name:zerocho} 가 됨. 유효기간이 지난 쿠키는 알아서 걸러짐

 

쿠키는 클라이언트에서 위조하기 쉬워서 비밀키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙임.서명 붙으면 쿠키가 name=zerocho.sign 과 같은 모양됨~서명된 쿠키는 req.cookies 대신 req.signedCookies 객체에 들어있음

 

 

cookie-parser가 쿠키 생성할 때 쓰이는 것은 아님!

 

쿠키 생성/제거 할때는 

res.cookie(키, 값, 옵션)

res.clearCookie 

메서드 사용해야함

 

옵션은 domain, expires,httpOnly,maxAge,path,secure 등있음

 

이렇게~

res.cookie("name""zerocho", {

  expires: new Date(Date.now() + 900000),

  httpOnly: true,

  secure: true,

});

res.clearCookie("name""zerocho", { httpOnly: truesecure: true });

 

쿠키를 지우려면 키와 값, 옵션도 정확히 일치해야 지워짐

단 expires 나 maxAge옵션은 일치할 필요 없어

 

signed 옵션은 true로 설정하면 쿠키 뒤에 서명이 붙음

서명을 위한 비밀 키는 cookieParser 미들웨어에 인수로 넣은 process.env.COOKIE_SECRET가 됨

 

 

 

express-session

세션 관리용 미들웨어

 

로그인 등 세션 구현하거나 특정 사용자 위한 데이터를 임시저장해둘 때 유용

세션은 사용자 별로 req.session 객체 안에 유지된다. 

app.use( session({

    resave: false,

    saveUninitialized: false,

    secret: process.env.COOKIE_SECRET,

    cookie: {

      httpOnly: true,

      secure: false,

    },

    name: "session-cookie",

  })

);

 

express-session 은 인수로 세션에 대한 설정을 받는다. 

resave는 요청이 올 때 세션에 수정 사항 생기지 않아도 세션을 다시 저장할지 설정하는 것

saveUninitialized 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정하는 것

둘다 필요없어서 false로 한거야~

 

express-session은 세션 관리시 클라이언트에 쿠키를 보냄 

4.3절에서 배운 세션 쿠키가 이거다 이말임   p240 여기서 보충설명 다시 읽어봐라..

 

 

 

미들웨어의 특성 활용하기

app.use((req,res,next)=>{

    console.log('모든 요청에 다 실행됩니다.');

    next();

})

 

미들웨어는 req,res,next를 매개변수로 가지는 함수로서 

app.use

app.get

app.post

등으로 장착함 

 

특정한 주소의 요청에만 미들웨어가 실행되게 하려면 첫 번째 인수로 주소를 넣으면 된다~

app.use(

  morgan("dev"),

  express.static("/"path.join(__dirname"public")),

  express.json(),

  express.urlencoded({ extended: false }),

  cookieParser(process.env.COOKIE_SECRET)

);

 

 

위처럼 동시에 여러 개의 미들웨어를 장착할 수도 있고 다음 미들웨어로 넘어가려면 next함수 호출해야함.

위 미들웨어들은 내부적으로 next 호출하고 있어서 연달아 쓸 수 있는거.

next 호출하지 않는 미들웨어는 res.send나 res.sendFile 등의 메서드로 응답 보내야함 

express.static과 같은 미들웨어는 정적파일을 제공할때 next 대신  res.sendFile 메서도로 응답보낸다. 

따라서 정적파일 제공하는 경우 

express.json, express.urlencoded,cookieParser 미들웨어는 실행되지 않음

미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을 수도 ㅇㅆ다느넉ㄹ 기억햊도오아

만약 next 도 호출하지 않고 응답도 보내지 않으면 클라이언트는 응답못받고 계속 기다리게 됨

 

app.use((reqresnext=> {

    req.data = '데이터 넣기';

    next();

  },(req,res,next)=>{

    console.log(req.data);//데이터 받기

    next();

  });

 

현재 요청이 처리되는 동안 req.data를 통해 미들웨어 간 데이터 공유할 수 있음

새로운 요청오면 req.data초기화됨 속성명이 꼭 data일 필요없지만 다른 미들웨어와 겹치지 않고 조심

app.set과의 차이 p244

app.set으로 익스프레스에서 데이터를 저장할 수 있다는 것을 배웠음 app.get 또는 req.app.get으로 어디서든지 데이터를 가져올 수 있음 하지만 app.set 사용안하고 req 객체에 데이터를 넣어서 다음 미들웨어로 전달해야하는 이유가 있다. app.set은 익스프레스에서 전역적으로 사용되므로 사용자 개개인의 값을 넣기에는 부적절하며 앱전체의 설정을 공유할 때 사용하면 됨 
req객체는 요청을 보낸 사용자 개개인에게 귀속되므로 req객체를 통해 개인의 데이터를 전달하는 것이 좋다

 

 

multer

멀티파트 형식으로 업로드할 때 사용하는 미들웨어

 

 

 

 

 

 

https://expressjs.com/ko/guide/routing.html

 

Express 라우팅

라우팅 라우팅은 애플리케이션 엔드 포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식을 말합니다. 라우팅에 대한 소개는 기본 라우팅을 참조하십시오. 다음 코드는 매우 기본

expressjs.com

 

728x90
728x90

템플릿 엔진 Template Engine

템플릿 양식과 특정 데이터 모델에 따른 입력 자료를 합성하여 결과 문서를 출력하는 소프트웨어(또는 소프트웨어 컴포넌트)를 말한다


* Template : 공통적인 프레임을 미리 제작한 것

*웹 템플릿 엔진(Web Template Enging)이란? 웹 문서가 출력되는 템플릿 엔진을 말합니다. 즉, 웹 템플릿 엔진은  웹 템플릿들(Web Templates)과 웹 컨텐츠 정보(Content Information)를 처리하기 위해 설계된 소프트웨어입니다.

또한 웹 템플릿 엔진은 View Code(Html)와 Data Logic Code(Db Connection)를 분리해주는 기능을 합니다.

 

오 이사람 설명 잘한다 

https://show-me-the-money.tistory.com/56

 

템플릿 엔진이란 무엇인가?

자, 여기 인형을 만드는 업자가 있습니다. 이 업자는 인형을 만들기 시작한지 얼마 되지 않아 바느질에 익숙하지 않습니다. 고객으로부터 원하는 인형을 주문 받으면 업자는 창고에서 몸통, 눈,

show-me-the-money.tistory.com

 

 

 

출처

 

웹기술 - 템플릿 엔진, template engine 이란 무엇인가

서버 데이터를 정적 파일에 간단히 넘겨주는 기능 template engine 템플릿 엔진(template engine) 은 앱에...

blog.naver.com

 

[Template Engine] 템플릿 엔진(Template Engine)이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

템플릿 엔진(Template Engine) 이란?

템플릿 엔진이란 템플릿 양식과 특정 데이터 모델에 따른 입력 자료를 합성하여 결과 문서를 출력하는 소프트웨어(또는 소프트웨어 컴포넌트)를 말합니다. * Template : 공통적인 프레임을 미리 제

usefultoknow.tistory.com

 

728x90
728x90

HTTP(Hyper Text Transfer Protocol)

웹 상에서 클라이언트와 서버가 서로 정보를 주고받을 수 있도록 하는 규약

우선 클라이언트는 서버에 정보(데이터) 전송을 요청(Request)할 수 있는 클라이언트 소프트웨어(크롬, IE, 사파리 등 웹 브라우저가 대표적)가 설치된 컴퓨터를 의미한다. 클라이언트는 URL(Uniform Resource Locator)로 된 HTTP를 통해 서버에게 정보 송신을 요청합니다. 우리가 평소 쓰는 URL 구조를 구분해 살피면 각각은 아래와 같은 의미를 가짐.

 

URL의 예: http://www.wishket.com/company-intro
1. http://: 자원에 접근하기 위한 http 프로토콜
2. www.wishket.com: 서버의 위치
3. company-intro: 서버에서 컴퓨터가 요청한 자원의 위치 

 

클라이언트가 이렇게 정보 송신을 요청하면 서버는 대응한다. 서버는 응답하는(Response) 소프트웨어(아파치, nginx, IIS 등)가 설치된 컴퓨터를 의미한다. 서버는 클라이언트의 요청을 해석하고 클라이언트의 요청 및 서버 관리자가 설정한 알고리즘에 준하는 정보를 클라이언트에게 송신한다.

 

 

 

위의 내용을 정리하면 HTTP를 통해 이런 일이 이루어짐~

1. 클라이언트가 보고 싶은 정보를 서버에게 HTTP를 통해 요청.
2. 서버는 알맞은 응답 메시지 및 정보를 클라이언트에게 전달.
3. 응답 메시지 및 정보 중 HTTP바디 내용이 클라이언트가 설정한 클라이언트의 용처에 도달한다.

 

 

 

HTTP의 구조

HTTP는 애플리케이션 레벨의 프로토콜로 TCP/IP 위에서 작동한다. HTTP는 상태를 가지고 있지 않는 Stateless 프로토콜이며 Method, Path, Version, Headers, Body 등으로 구성된다.


하지만 HTTP는 암호화가 되지 않은 평문 데이터를 전송하는 프로토콜이라서 보안 취약

이 문제 해결하기 위해 HTTPS가 등장

 

HTTPS

HyperText Transfer Protocol over Secure Socket Layer, HTTP over TLS, HTTP over SSL, HTTP Secure 등으로 불리는 HTTPS는 HTTP에 데이터 암호화가 추가된 프로토콜이다. HTTPS는 HTTP와 다르게 443번 포트를 사용하며, 네트워크 상에서 중간에 제3자가 정보를 볼 수 없도록 공개키 암호화를 지원하고 있다.

 

‘HTTP vs HTTPS 차이’

 

바로 SSL(보안 소켓 계층) 인증서

 

사실 HTTPS는 쉽게 말해서 HTTP 프로토콜에 보안 기능을 추가한 것

 

SSL 인증서는 사용자가 사이트에 제공하는 정보를 암호화하는데, 쉽게 말해서 데이터를 암호로 바꾼다고 생각하면 쉽다. 이렇게 전송된 데이터는 중간에서 누군가 훔쳐 낸다고 하더라도 데이터가 암호화되어있기 때문에 해독할 수 없다. 그 외에도 HTTPS는 TLS(전송 계층 보안) 프로토콜을 통해서도 보안을 유지. TSL은 데이터 무결성을 제공하기 때문에 데이터가 전송 중에 수정되거나 손상되는 것을 방지하고, 사용자가 자신이 의도하는 웹사이트와 통신하고 있음을 입증하는 인증 기능도 제공하고 있습니다

 

 

사실 HTTPS로 전환하게 되면 검색엔진 최적화(SEO)에 있어서도 큰 혜택을 볼 수 있음

또한 가속화된 모바일 페이지(AMP, Accelerated Mobile Pages)를 만들고 싶을 때도 HTTPS 프로토콜을 사용해만 함 

 

 

 

출처

 

 

728x90
728x90

클라이언트가 보내는 요청의 단점, 누가 보낸 건지 몰라. 

대신 로그인 구현하면 되는데 로그인 한후에 새로고침해도 로그아웃 안되도록 

클라이언트가 서버에 누구인지 지속적으로 알려줘야함

쿠키

클라이언트 로컬에 저장되는

유효기간 있고 name=zerocho 처럼 단순한 Key-Value쌍의 작은 데이터 파일

 

쿠키의 구성요소

1. 이름 : 각각의 쿠키를 구별하는데 사용함

2. 유효시간: 쿠키의 유지시간

3. 도메인: 쿠키를 전송할 도메인

4. 경로: 쿠키를 전송할 요청 경로

5. 값: 쿠키와 이름과 관련된 값

 

 

 

동작방식

1. 클라이언트 -> 페이지 request 요청

2. 서버에서 쿠키를 생성

3. http헤더에 쿠키를 포함해서 respond 응답

4. 브라우저가 종료되어도 쿠키의 기한이 정해져 있지 않고 명시적으로 지우지 않는다면 반 영구적으로 쿠키가 남아있게됨

클라이언트 요청(쿠키가지지 않은 상태)
------------->
쿠키와 함께 응답
<-------------
쿠키와 함께 요청
--------------->
응답(쿠키 가진 상태)
<---------------
서버

 

 

 

세션 

브라우저가 종료되기 전까지 클라이언트의 요청을 유지하게 해주는 기술

 

세션은 어쨌든 쿠키를 기반으로 하고 있음

서버측에서 관리하긴함

 

 

 

사용자 많아질수록 서버 메모리 많이 먹어

클라이언트한테 아이디부여하는데 그게 세션아이디임

 

동작방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버는 클라이언트의 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) unique한 id를 sessionid라는 이름으로 저장
  3. 서버가 응답할 때 응답헤더에 set-cookie: sessionid:a1x2fjz를 추가하여 응답

   4 . 클라이언트는 이후 서버에 요청할 때 전달받은 sessionid:a1x2fjz쿠키를 자동으로 요청헤더에 추가하여 요청

   5. 서버에서는 요청헤더의 sessionid 값을 저장된 세션저장소에서 찾아보고 유효한지 확인후 요청을 처리하고 응답

 

세션의 내용은 서버에 저장되기 때문에 계속하여 늘어날 경우 서버에 부하가 발생

 

 

쿠키와 세션의 차이점

가장 큰 차이점 정보가 저장되는 위치

 

저장위치

쿠키는 서버에 저장되지 않고 클라이언트 로컬에 저장됨

세션은 서버의 자원을 써서 로컬과 서버에 저장됨

 

장단점

보안면에서는 세션이 좋고

속도면에서는 쿠키가 빠름

 

세션은 쿠키를 이용해 id만 저장하고 서버에 저장해

세션도 만료시간 정할 수 있지만 브라우저가 종료되면 만료시간 상관없이 날라감

쿠키는 브라우저 종료해도 파일로 남아있음

 

 

 

얘를 보완해서 JWT가 나옴 책에도 나와~

 

 

 

 

 

책보면서 직접 코드 따라해보기

 

cookie.js  

const http = require("http");
http
  .createServer((req, res) => {
    console.log(req.url, req.headers.cookie);
    res.writeHead(200, { "Set-Cookie": "mycookie=test" });
    res.end("Hello Cookie");
  })
  .listen(8083, () => {
    console.log("8083번 포트에서 서버 대기 중입니다");
  });

 

터미널

 경로 입력하고 서버대기 중 확인

 

 

createServer 메서드의 콜백에서는 req 객체에 담겨있는 쿠키 가져옴

쿠키는

req.headers.cookie 에 들어있음

req.headers는 요청의 헤더 의미 (쿠키는 요청과 응답의 헤더를 통해 오감)

응답의 헤더에 쿠키를 기록해야 돼서

res.writeHead 메서드를 사용함 

"Set-Cookie": "mycookie=test" 는 "다음에 오는 쿠키를 저장해라 : 다음"

 

 

 

 

로컬주소에 접속

req.url과 req.headers.cookies에 대한 정보를 로깅하도록 함

req.url은 주소의 path와 search부분을 알림 (사실 이부분 이해안돼서 찾아봤는데 이해안됨 그래도 읽어보자)

아 이건인가봐 (뒤늦게 발견)

 

 

 

 

위 코드까지는 쿠키 심기만 한거고

그 쿠키가 나인지를 식별 못하고 있음

이제는 사용자를 식별하는 방법을 알아보자

 

cookie2.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>쿠키&세션 이해하기</title>
  </head>
  <body>
    <form action="/login">
      <input id="name" name="name" placeholder="이름을 입력하세요" />
      <button id="login">로그인</button>
    </form>
  </body>
</html>

cookie2.js

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");
//쿠키는 문자열임 이를 쉽게 사용하기 위해 js객체 형식으로 바꾸는  parseCookies함수
const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});
http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);

    //주소가 /login으로 시작하는 경우,url과 querystring모듈로 각각 주소와 주소에 딸려오는 query분석한다
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      //쿠키 유효시간을 현재시간 +5분으로 설정
      expires.setMinutes(expires.getMinutes() + 5);
      //헤더에 302응답코드,리다이렉트 주소와 함께 쿠키를 넣어
      //브라우저는 이 응답코드를 보고 페이지를 해당주소로 리다이렉트함
      //헤더는 한글을 설정할 수 없어서 name변수를encodeURIComponent메서드로 인코딩함
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `name=${encodeURIComponent(
          name
        )}; Expires=${expires.toGMTString()};HttpOnly;path=/`,
      });
      res.end();

      //그 외의 경우(/로 접속했을때 등),먼저 쿠키있나없나확인. 없으면 로그인할 수 있는페이지보냄
      //처음 방문한경우 쿠키없으므로 cookie2.html 전송됨 쿠키있다면 로그인한상태로 간주해인사말보냄
      //name이라는 쿠키가 있는 경우
    } else if (cookies.name) {
      res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
      res.end(`${cookies.name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
        res.end(data);
      } catch (err) {
        res.writeHead(500, { "Content-Type": "text/plain;charset=utf-8" });
        res.end(err.message);
      }
    }
  })
  .listen(8084, () => {
    console.log("8084번 포트에서 서버 대기중입니다!");
  });

set-Cookie로 쿠키 설정할 때 

만료시간 expires 과 HttpOnly, Path같은 옵션 부여함

쿠키 설정할 때 각종 옵션 넣을 수 있고 옵션 사이 세미콜론(;) 써서 구분

쿠키에는 한글 쓰기, 줄바꿈 하면 안됨

  • 쿠키명=쿠키값  기본적 쿠키값 예) mycookie=test
  • Expires=날짜: 만료기한. 기본값은 클라이언트가 종료될 때까지
  • Domain= 도메인명:  쿠키가 전송될 도메인 특정할 수 있음. 기본값은 현재 도메인
  • Path=url : 쿠키가 전송될 url을 특정할 수 있음 기본값은'/'이고 이경우 모든 url에서 쿠키 전송할 수 있음
  • secure: HTTPS 일 경우에만 쿠키가 전송됨
  • HttpOnly: 설정 시 자바스크립트에서 쿠키에 접근할 수 없음 쿠키 조작을 방지하기 위해 설정하는 것이 좋음

 

 

 

 

 

이제 이름 노출안되는 버전

세션 방식.

const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");

const parseCookies = (cookie = "") =>
  cookie
    .split(";")
    .map((v) => v.split("="))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

const session = {};
http
  .createServer(async (req, res) => {
    const cookies = parseCookies(req.headers.cookie);
    if (req.url.startsWith("/login")) {
      const { query } = url.parse(req.url);
      const { name } = qs.parse(query);
      const expires = new Date();
      expires.setMinutes(expires.getMinutes() + 5);
      const uniqueInt = Date.now();
      session[uniqueInt] = {
        name,
        expires,
      };
      res.writeHead(302, {
        Location: "/",
        "Set-Cookie": `session=${uniqueInt}; Expires=${expires.toGMTString()};HttpOnly;Path=/`,
      });
      res.end();
      //세션 쿠키가 존재하고 만료기간이 지나지 않았다면
    } else if (
      cookies.session &&
      session[cookies.session].expires > new Date()
    ) {
      res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
      res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else {
      try {
        const data = await fs.readFile("./cookie2.html");
        res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
        res.end(data);
      } catch (err) {
        res.writeHead(500, { "Content-Type": "text/plain;charset=utf-8" });
        res.end(err.message);
      }
    }
  })
  .listen(8085, () => {
    console.log("8085번 포트에서 서버 대기 중입니다");
  });

 

코드 따라썼는데 자꾸 안된다고 에러뜸 

바로 이 에러~!!

NodeJS address already in use 

찾아서 따라해서 이전 포트? 종료하고 다시 함

로컬주소 들어가서 정보 입력하니까!

 

 아까 넣었던 쿠키 위에 쌓였다.

이 방식이 세션임

 

 

아까랑 다른 건 쿠키에 이름을 담아서 보내는 대신, uniqueInt라는 숫자값을 보냄. 사용자의 이름과 만료시간은 uniqueInt 속성명 아래에 있는 session이라는 객체에 대신 저장함

이제 cookie.session이 있고 만료기한 넘기지 않았으면 session 변수에서 사용자 정보를 가져와 사용한다.

 

 

안전하게 사용하기 위해서는 다른 사람들이 만든 검증된 코드를 사용하는게 좋대

그건 책 5장에서~~~

 

 

 

 

 

 

 

 

그.런.데.

내가 코드를 다 이해했다? 전혀 아니지요.

 

map 메서드

배열.map((요소, 인덱스, 배열) => { return 요소 });

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.map((v) => {
  console.log(v);
  return v;
});
// 콘솔에는 1, 2, 3이 찍힘
oneTwoThree; // [1, 2, 3]
result; // [1, 2, 3]
oneTwoThree === result; // false

reduce 메서드

배열.reduce((누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);

result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 1 0
// 1 2 1
// 3 3 2
result; // 6

acc(누적값)이 초깃값인 0부터 시작해서 return하는대로 누적되는 것을 볼 수 있습니다. 초깃값을 적어주지 않으면 자동으로 초깃값이 0번째 인덱스의 값이 됩니다.

 

그렇다고 함 이것이 바로 설명 링크.. 읽어도 잘모르겠지만 암튼 보자 

 

 

 

 

 

 

 

NodeJS address already in use 문제 해결 - JooTC

NodeJS address already in use 문제 해결 방법 Error: listen EADDRINUSE: address already in use :::5000 현재 다른 프로세스에서 사용 중이라 해당 서비스를 시작할 수 없다는 에러입니다.

jootc.com

 

 

 

 

 

출처

728x90
728x90

개념 정리 <Node.js교과서>- 조현영 지음 책 요약

 

Node.js®는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임입니다.

 

 

기본개념

 

1.서버

네트워크를 통해 클라이언트레 정보나 서비스를 제공하는 컴퓨터 또는 프로그램

 

노드는 서버 애플리케이션 실행하는데 제일 많이 사용됨.

서버는 클라이언트의 요청에 대해 응답해야 함. 

 

 

2. 자바스크립트 런타임

노드는 자바스크립트 런타임. 런타임은 특정 언어로 만든 프로그램들을 실행할 수 있는 환경을 뜻함

 

 

<노드의 내부 구조>

Node.js Core Library
Node.js Bindings
V8 (  -> 오픈소스 자바스크립트 엔진) libuv (  -> 비동기 I/O)

 

3. 이벤트 기반 event-driven

이벤트(클릭, 네트워크 요청 등과 같은)가 발생할 때 미리 지정해둔 작업을 수행하는 방식 의미한다.

특정 이벤트가 발생할 때 무엇을 할지 미리 등록해놔야하는데 이걸 이벤트 리스너에 콜백함수를 등록한다고 표현한다.

발생한 이벤트가 없거나 발생했던 이벤트를 다 처리하면 노드는 다음 이벤트가 발생할 때까지 대기.

 

<이벤트 기반>

시스템 2. 이벤트 발생
--------------------->

<---------------------
3. 등록된 콜백함수 호출
이벤트 리스너

1. 이벤트 리스너에 콜백함수 등록

이벤트 기반 모델에서는 이벤트 루프라는 개념 등장. 여러 이벤트 동시발생시 어떤 순서로 콜백함수를 호출할지를 이벤트 루프가 판단함.  노드와 자바스크립트에서 이벤트 루프는 정말 중요 개념~!

 

이벤트 루프:
이벤트 발생시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행순서결정하는 역할 담당. 노드가 종료될 때까지 이벤트 처리위한 작업을 반복해서 루프라고 부름.
백그라운드:
setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳. 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 된다. 여러 작업이 동시에 실행될 수 있음
테스크 큐:
이벤트 발생 후, 백그라운드에서는 테스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보낸다. 정해진 순서대로 콜백들이 줄 서 있어서 콜백 큐라고 부른다. 보통은 완료된 순서대로 서있지만 특정한 경우 순서 바뀌기도함.

 

4. 논 블로킹 I/O 

 

작업은

동시에 실행될 수 있는 작업과

동시에 실행될 수 없는 작업있음 

 

자바스크립트 코드는 동시에 실행될 수 없음

하지만 js상에서 돌아가는게 아닌 입력Input 출력Output 작업같은 건 동시처리 가능

 

(파일 시스템 접근이나 네트워크를 통한 요청 같은 작업이 I/O의 일종)

 

이런 작업시 노드는 

논블로킹 방식으로 처리하는 방법을 제공한다. 

 

<백그라운드 작업완료 확인 여부>
논블로킹:
이전 작업이 완료될 때까지 대기하지 않고 다음작업 수행
블로킹:
이전 작업 끝나야만 다음작업 수행

 

 

노드는 I/O 작업을 백그라운드로 넘겨 동시에 처리하곤 함.

작업 순서에 따라 작업 시간 단축되어 성능 크게 달라짐! 동시에 처리될 수 있는 I/O 작업이라도 논블로킹 방식으로 코딩하지 않으면 의미 퇴색되므로 논 블로킹 방식으로 코딩하는 습관 들이기!!

 

하지만 아무리 논블로킹 방식으로 코드 작성해도 우리가 전부 작성한 코드는 서로 동시에 실행되지 않기에 소요시간이 짧아지진 않는다. 실행 순서만 바뀔뿐. 그래도 논블로킹을 통해 실행 순서를 바꿔줌으로써 그 작업 때무에 간단한 작업들이 대기하는 상황을 막을 수 있다는 점에서 의의가 있다. 

논블로킹과 동시가 같은 의미가 아니라는 것도 알아두기 . 동시성은 동시 처리가 가능한 작업을 논 블로킹 처리해야 얻을 수 있음.

노드에서는 동기와 블로킹이 유사하고 비동기와 논블로킹이 유사함.

<함수가 바로 return되는지 여부>
동기(synchronous : 동시에 일어나는):
블로킹 방식에서는 백그라운드 작업 완료 여부를 계속 확인하며, 호출한 함수가 바로 return되지 않고 백그라운드 작업이 끝나야 return된다. 
비동기(Asynchronous : 동시에 일어나지 않는):
논블로킹 방식에서는 호출한 함수가 바로 return되어 다음 작읍으로 넘어가며, 백그라운드 작업 완료 여부는 신경쓰지 않고 나중에 백그라운드가 알림을 줄 때 비로소 처리함.

 

동기:  요청과 결과가 한 자리에서 동시에 일어남.설계가 매우 간단하고 직관적이지만 과가 주어질 때까지 아무것도 못하고 대기해야 하는 단점이 있고, 

비동기:  요청한 그 자리에서 결과가 주어지지 않음 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있습니다.
출처: https://private.tistory.com/24 [공부해서 남 주자]

 

 

 

5. 싱글스레드

 

스레드가 하나뿐이라는 것.

 

프로세스:  운영체제에서 할당하는 작업의 단위.

노드나 웹브라우저 같은 프로그램은 개별적인 프로세스임. 프로세스 간에는 메모리등의 자원 공유안해

스레드: 프로세스 내에서 실행되는 흐름의 단위.

프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있음. 스레드들은 부모 프로세스의 자원공유해. 같은 주소의 메모리에 접근 가능해 데이터를 공유할 수 있어

 

 

 

노드는 싱글 스레드?

엄밀히 말하면 싱글 스레드로 동작하지 않음

노드를 실행하면 먼저 프로세스 하나 생성됨 그 프로세스 안에서 스레드 여러개 생성함 . 우리가 직접 제어할 수 있는 스레드는 하나뿐! 그래서 흔히 노드가 싱글 스레드라고 여겨짐.

스레드는 작업을 처리하는 일손. 하나의 스레드만 직접 조작할 수 있으니까 일손이 하나인 것임.

블로킹이 발생할 것 같은 경우에는 논블로킹 방법으로 대기시간을 최대한 줄인다. 

 

 

<노드가 싱글 스레드로 동작하지 않는 두 가지 경우>

스레드풀(Thread Pool)

노드가 특정 동작을 수행할 때 스스로 멀티 스레드를 사용한다. 대표적인 예로 암호화(3.5.5절), 파일입출력(3.6절),압축(3.6.2절) 등이 있다

워커 스레드(Worker Thread)

이제 노드에서도 멀티 스레드를 사용할 수 있게한 기능. 우리가 직접 다수의 스레드를 다룰 수 있음. CPU 작업(연산이 많은 작업)이 많은 경우 워커 스레드를 사용하면 된다.

 

 

 

서버로서의 노드

 

노드는 기본적으로 싱글 스레드, 논블로킹 모델을 사용함. 

따라서 노드 서버의 장단점은 싱글 스레드, 논블로킹 모델의 장단점과 비슷함.

 

노드는 CPU부하가 큰 작업에는 적합하지 않음 연산을 많이 요구하면 스레드 하나가 혼자 감당하기 어려워!

노드는 개수는 많지만 크기는 작은 데이터를 실시간으로 주고받는데 적합함.

네트워크나 데이터베이스, 디스크 작업 같은 I/O에 특화되어있음

 

<노드의 장단점>

장점 단점
멀티 스레드 방식에 비해 적은 컴퓨터 자원 사용 기본적으로 싱글 스레드라서 CPU코어를 하나만 사용
I/O 작업이 많은 서버로 적합 CPU작업이 많은 서버로는 부적합
멀티 스레드 방식보다 쉬움 하나뿐인 스레드가 멈추지 않도록 관리가 필요함
웹 서버가 내장되어있음 서버 규모가 커졌을 때 서버를 관리하기 어려움
자바스크립트를 사용함 어중간한 성능
JSON형식과 쉽게 호환됨  

 

 

 

 

 

 

 

728x90

+ Recent posts