유저 API를 통해 복습하는 http

carrot·2022년 8월 19일
0

http

목록 보기
1/2
post-thumbnail

인프런 - 모든 개발자를 위한 HTTP 웹 기본 지식
해당 포스팅은 위 강의를 수강하고 난 뒤 기존에 작성된 개인 프로젝트의 코드를 고치고 강의 내용을 적용해 보는 일련의 과정을 담고 있습니다.
코드의 부족한 점, http 관련 잘못된 내용들에 대해서는 댓글로 피드백 주시면 감사하겠습니다. 👨🏼‍🌾

웹 서비스를 이용하기 위해 회원가입을 하면 로그인, 프로필 업데이트, 로그아웃, 회원탈퇴 등 크게 4가지의 기능을 이용할 수 있습니다.

클라이언트에서 유저가 이 4가지 기능을 사용할 때 발생하는 서버와의 통신 과정을 어떻게 구성했는지 살펴보고 http 학습 내용을 바탕으로 해석해 보도록 하겠습니다.

1. 로그인

login request function

export const loginAPI = (body: LoginAPIBody) =>
  axios({
    url: API_AUTH_LOG_IN,
    method: "POST",
    data: body,
    headers: {
      "carrot-status": "wilted",
    },
  });
  • 로그인에 필요한 정보를 메시지 바디에 담아 전송합니다.
  • 리퀘스트 헤더에 커스텀 값으로 carrot의 상태 정보를 포함합니다.

400번대와 500번대 응답 코드로 살펴보는 로그인 실패 사례

if (req.method !== "POST") {
  res.statusCode = 405;
  return res.end();
}
  • 로그인 요청 메서드가 POST가 아닌 경우 405 Method Not Allowed 상태코드로 응답합니다.
    요청 메서드가 잘못 사용되어 요청을 처리할 수 없는 상태를 의미합니다.
const { email, password } = req.body;
if (!email || !password) {
  res.statusCode = 400;
  return res.send("필수 정보가 없습니다.");
}
  • 로그인에 필요한 필수 데이터가 없는 경우 400 Bad Request 상태코드로 응답합니다.
    클라이언트의 잘못된 요청으로 인해 요청을 처리할 수 없는 상태를 의미합니다.
const { User } = await connect();
const user: StoredUserType = await User.findOne({ email: email }).exec();

if (!user) {
  res.statusCode = 404;
  return res.send("해당 이메일에 해당하는 유저가 없습니다.");
}
  • 요청된 이메일에 해당하는 유저 정보가 없는 경우 404 Not Found 상태코드로 응답합니다.
    요청받은 리소스를 찾을 수 없는 상태임을 의미합니다.
if (user && !comparePassword(password)) {
  res.statusCode = 401;
  return res.send("비밀번호가 일치하지 않습니다.");
}
  • 유저 정보가 존재하나 패스워드가 일치하지 않는 경우 401 Unauthorized 상태코드로 응답합니다.
    서버에 자원이 존재하지만 인증에 대한 자격 증명이 없는 상태를 의미합니다.
const { User } = await connect();
const user: StoredUserType = await User.findOne({ email }).catch(
  (error: MongoError) => res.status(502).send(error.message)
)
  • 서버에서 MongoDB 서버에 접근해서 프로세스를 처리하는 도중에 에러가 발생한다면 502 Bad Gateway 상태코드로 응답합니다.
    이는 응답 서버가 아닌 외부 서버에서 에러가 발생했음을 클라이언트에 알려줍니다.
  • 클라이언트 요청에 대한 문제 이외에 에러가 발생하면 500 Internal Server Error 상태코드로 응답합니다.
    개발자가 에러의 책임을 서버에게 던지고 응답을 마무리하는 무책임한 상태를 의미합니다.
res.setHeader(
  "Set-Cookie",
  `access_token=${token}; path=/; expires=${new Date(
    Date.now() + 60 * 60 * 1000
  ).toUTCString()}; httponly`
);
return res.status(200).send(userdataWithoutPassword)
  • 모든 난관을 뚫고 로그인에 성공하면 생성한 인증 토큰을 헤더 쿠키에 설정합니다.
  • 쿠키의 접근 범위를 BaseURL의 모든 하위 경로로 설정해주고 만료 일자를 설정합니다.
  • 보안 옵션 사항으로 httponly를 추가합니다. 이 옵션은 자바스크립트에서 쿠키에 대한 접근이 불가능하도록 하여 XSS 공격 방지합니다.

headers

개발자도구 네트워크 탭에서 login api에 대한 header 명세를 보면 각각 요청과 응답에 설정한 헤더 값들이 잘 전송되고 있는 것을 확인할 수 있습니다.

  • General Headers에 requeset method, status code 등의 정보가 있습니다.
  • Response Headers에 검증과 관련된 ETag 헤더가 부여되었습니다. 이 외에 요청 헤더에 따른 콘텐츠 타입 정보와 응답 생성 날짜 정보가 추가되었습니다.
  • Reqeust Header에 negotiation에 관련된 Accept 헤더 3개가 추가되었습니다. 선호하는 미디어의 타입, 인코딩 방법, 자연언어의 정보를 서버에 전달하고 있습니다.
    Referer값을 통해 로그인 하기 전에 메인 페이지에 위치하고 있었음을 알 수 있습니다.
    User-Agent값을 통해 사용자 정보를 확인할 수 있으나 맹신할 수는 없는 정보입니다.

2. 로그아웃

로그아웃 요청 함수와 응답 함수의 코드를 살펴봅니다.

export const logoutAPI = (userId: string) =>
  axios({
    url: `${API_AUTH_LOG_OUT}/${userId}`,
    method: "DELETE",
  });
  • DELETE 메서드를 통해 로그아웃 요청을 보냅니다.
  • 로그아웃 프로세스에 필요한 정보인 유저의 아이디는 쿼리스트링으로 전달합니다.
  • 별도의 본문 데이터가 필요하지 않으므로 data 값은 생략합니다.
export default async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method === "DELETE") {
    try {
      const { User } = await connect();
      await User.findByIdAndUpdate(req.query.id, { token: "" }).exec();

      res.setHeader("Set-cookie", "access_token=; path=/; expires=; httponly");
      return res.status(200).end();
    } catch (error) {
      res.statusCode = 500
      return res.send(error)
    }
  }

  return res.status(405).end();
};
  • MongoDB에 접근하거나 접근해서 토큰을 제거하는 프로세스에서 에러가 발생한다면 502 Bad Gateway 상태코드로 응답합니다. 이 응답코드는 응답 서버가 다른 서버로부터 유효하지 않은 응답을 받았다는 것을 의미합니다.
  • 이외 응답 서버에서 문제가 발생하면 500 Internal Server Error 상태코드를 응답합니다.
  • 로그아웃 요청이 정상적으로 처리되면 200 OK 상태코드와 Header에 포함된 쿠키 값, 만료 시간 등을 초기화한 뒤 응답합니다.
  • 요청이 완료되면 클라이언트에 전달할 별도의 데이터 본문이 없기 때문에 상태 코드를 전달하고 응답을 종료합니다.

headers

로그아웃이 정상적으로 처리되고 난 뒤 headers 정보 입니다.

  • DELETE method가 정상적으로 처리되었음을 General Headers에서 확인할 수 있습니다.
  • Response Headers에서 쿠키에 토큰값과 만료기간이 정상적으로 초기화 된 것을 확인할 수 있습니다.
  • Request Headers에서 유저가 로그아웃 하기 전에 게시판의 게시글을 읽고 있었음을 확인할 수 있습니다.
    Referer 헤더값으로 유저의 서비스 이용 패턴에 대한 정보를 얻을 수 있습니다.

3. 프로필 업데이트

유저의 프로필 사진과 이름을 변경 요청하는 API

export const updateProfileImageAPI = ({ _id, profileImage }: UserProps) =>
  axios.patch(API_SETTING_USER, { _id, profileImage });

export const updateUserNameAPI = ({ _id, name }: UserProps) =>
  axios.patch(API_SETTING_USER, { _id, name });
  • PATCH 메서드를 사용하여 요청합니다.
    PATCH 메서드를 사용한 이유는, 위 API는 로그인 인증을 거친 유저에게 제공되는 기능이기 때문에 유저 데이터가 존재할 거라는 근거로 사용했습니다.
  • 메시지 바디에 유저 리소스를 식별할 아이디와 변경할 값을 포함하여 요청합니다.

update 요청에 대한 response

export default async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method === "PATCH") {
    User.findByIdAndUpdate(
      { _id: req.body._id },
      { [Object.keys(req.body)[1]]: Object.values(req.body)[1] },
      { new: true }
    ).catch(catcher);

    res.statusCode = 201;
    return res.end();
  }

  res.statusCode = 405;
  return res.end();
};
  • 요청 본문의 유저 아이디를 통해 DB에서 유저 정보를 식별하고 profileImage 또는 name 값을 수정합니다.
  • 작업에 성공하면 201 Created 상태코드를 응답합니다.
  • 요청 프로세스에 대한 별도의 응답 데이터가 없으므로 연결을 종료합니다.
  • PATCH 이외의 메서드 접근은 405 Method Not Allowed 상태코드를 응답하고 연결을 종료합니다.

headers

  • General Headers에서 PATCH 요청이 201 Created 응답을 받았음을 알 수 있습니다.
  • Request Headers에서 쿠키에 토큰 값이 포함되어 요청된 것을 확인할 수 있습니다.
  • Referer 정보를 통해 로그인 유저가 접근할 수 있는 /setting 경로에서 요청이 온 것을 확인할 수 있습니다.

4. 회원탈퇴

유저의 회원정보 삭제를 요청하는 API

export const secessionAPI = (_id: string) =>
  axios({
  	url: `${API_AUTH_USER}/${_id}`,
    method: "DELETE",
  })
  • DELETE 메서드를 통해 로그아웃 요청을 보냅니다.
  • 로그아웃 프로세스에 필요한 정보인 유저의 아이디는 쿼리스트링으로 전달합니다.

회원정보 삭제 요청에 대한 response

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { User, Board, Comment, Replies } = await connect();
  const { id } = req.query;

  if (req.method === "DELETE") {
    await User.deleteOne({ _id: id }).catch(catcher);
    await Board.deleteMany({ author: id }).catch(catcher);
    await Comment.deleteMany({ author: id }).catch(catcher);
    await Replies.deleteMany({ author: id }).catch(catcher);

    res.setHeader("Set-cookie", "access_token=; path=/; expires=; httponly");
    return res.end();
  }
  return res.status(405).end();
};
  • DELETE 메서드 요청이 들어오면 쿼리 스트링으로 전달받은 유저 아이디로 DB에서 데이터를 식별하고 삭제 처리를 합니다.
  • 응답 헤더에서 쿠키에 토큰 값을 삭제하고 만료시간을 초기화 합니다.
  • 요청 메서드가 다른 경우 405 Method Not Allowed 상태코드를 응답합니다.
  • 요청된 작업이 완료되어도 별도의 전달할 내용이 없으므로 응답을 종료합니다.

headers

  • 회원탈퇴 요청이 DELETE 메서드로 요청되었고 정상적으로 처리되었다는 200 OK 응답 코드를 받았습니다.
  • 예제 코드에서 알 수 있듯이 요청 성공시 응답 코드를 생략하고 응답을 종료했는데 200 OK를 응답받은 것으로 보아 default값인 것으로 추정됩니다.
  • set-cookie 헤더에서 토큰값과 만료시간을 초기화 시켰고 잘 적용된 것을 확인할 수 있습니다.

정리

  • http는 클라이언트와 서버의 대화 방법에 대한 약속이며 이는 프론트엔드와 백엔드 의사소통 기반이라고 할 수 있습니다.
  • 400번대 응답 상태 코드에 대해 문제의 원인이 클라이언트에 있음을 확인하고 로그인 실패 원인을 찾을 수 있으며, 코드를 재검토하고 문제를 해결할 수 있습니다.
  • 500번대 응답 상태 코드에 대해서 서버 개발자 또는 백엔드 개발자에게 문제의 해결 요청을 할 수 있습니다.
  • 의미 있는 상태 코드를 사용함으로써 에러 핸들링에 들어가는 시간 비용을 줄일 수 있습니다.

학습 리뷰

  • 오늘날 인터넷으로는 HyperText만을 전달하지 않습니다. 다양한 미디어타입의 데이터들를 요청하고 전송하며 이러한 데이터 자원들을 바탕으로 다양한 분야에서 부가가치를 생산하고 있습니다.
  • http는 프로토콜로 통신에 관한 규약이지만 다른 관점에서 바라보면 언어라고 생각합니다.
profile
당근같은사람

0개의 댓글