출처 내용을 토대로 복습용으로 적은 것 입니다.
클라이언트 애플리케이션은 백엔드에 위치한 서버와 통신하여
현재 로그인한 사용자의 정보를 받아오거나, 새로운 글을 개시하기도 하고
Web Socket를 통해 서버에서 발생한 이벤트를 구독하여
푸시 메시지나 채팅과 같은 기능을 구현하기도 한다.
이 과정에서 프론트엔드와 백엔드는 어떤 방식으로 통신을 할 것인지부터 시작하여
리소스의 생성과 삭제는 어떻게 정의할 것인지
프론트엔드에서 요청한 백엔드 작업의 성공/실패 여부는 어떻게 알려줄 것인지 등
많은 규칙들을 정의해야한다.
이러한 규칙을 정의할 때 도움을 주는 몇 가지 가이드라인이 있다.
이 때 등장하는 것들이 HTTP 메서드나 상태 코드같은 표준과 REST 같은 녀석들이다.
이 중에서 프론트엔드와 백엔드 간의 통신을 할 때
조금 더 명확한 정의를 위해 필요한 요소 중 하나인 HTTP 상태 코드를 파헤쳐 보려 한다.
굳이 가이드라인을 지켜야 하는가?
HTTP 메서드나 상태 코드, REST 같은 것들은 말 그대로 가이드라인이다.
이것들을 지키지 않는다고 해서 프로그램이 작동하지 않는 것도 아니고
사용자가 프로그램을 사용하던 도중 런타임 에러가 발생하는 오류는 생기지 않는다.
즉, 지키지 않아도 프로그램을 작성하는데는 아무런 지장이 없다.
규칙들을 지키지 않는 것은 자유지만
그로 인해 생기는 사이드 이펙트들을 생각해보면
되도록 지키는게 맞는 것 같다.
표준 인터페이스의 존재 이유를 생각해보자
산업 표준은 불특정 다수에 의해 생산되는 제품들의 호환성을 맞추고
제품 생산자들 간의 커뮤니케이션을 원할하게 하기 위해 제정된다.
각자 다른 객체들을 호환하기 위해 정의하는 일련의 표준 규격을
인터페이스(Interface)
라고 한다.
인터페이스라는 개념은 광범위하다.
모니터와 본체를 연결하는 HDMI
저장장치에 사용되는 SATA, USB
사람과 기계의 연결 UI(User Interface)
개발자에게 친숙한 인터페이스는 API (Application Programming Interface) 이다.
API는 응용 프로그램을 제작할 때 필요한 기능들을 일련의 인터페이스로 제공하는 것을 의미한다.
이 때 API를 사용하는 쪽에서는 API의 사용법만 알면 되고
그 이면에 어떤 거대한 로직들이 숨어있는지는 일절 관심을 끊어도 되기 때문에 굉장히 편리한 장점이 있다.
import org.springframework.web.bind.annotation.GetMapping;
/*
2. 화면에 {message:"helloworld"} 라고 출력됩니다.
*/
@GetMapping(value = "/helloworld/json")
@ResponseBody
public Hello helloworldJson() {
Hello hello = new Hello();
hello.message = "helloworld";
return hello;
}
개발자는 org.springframework.web.bind.annotation.GetMapping;
이 어떻게 작동하는지 몰라도(알면 좋지만) GetMapping 이라는 API 함수를 쓰면
Get 방식으로 URI에 Mapping 되도록 사용할 수 있다.
(예시가 잘 맞는지 모르겠다)
즉, API는 프로그램 간의 통신을 위한 인터페이스라고 할 수 있다.
마찬가지로 클라이언트가 서버에 뭔가를 요청할 때도
특정 규칙으로 정의된 API를 사용하여 서버의 리소스를 사용하게 되는데
HTTP를 사용하여 통신하는 대부분의 모던 애플리케이션에서는
이 API를 엔드포인트(endpoint)라고 불리는 특정한 URL을 사용하여 정의하게 되며
서버는 일관된 방식으로 이 엔드포인트로 들어온 클라이언트 요청에 대한 응답을 보내줘야 한다.
이 때 HTTP 상태 코드는 클라이언트가 보냈던 요청의 수행 결과를 의미하는 일종의 약속이며
API를 구성하는 중요한 요소 중 하나이다.
백엔드는 잘 모르는 프론트엔드의 슬픈 사정
이 섹션에서는 잘못 정의된 API를 사용하는 프론트엔드 개발자라면 한 번쯤 겪어보았음직한 일을 적으려 한다.
아마 백엔드 개발자들은 프론트엔드 애플리케이션의 소스를 직접 보는 경우가 드물기 때문에
이런 상황이 있다는 사실 조차 모를 수도 있다고 한다.
바로 HTTP 상태 코드를 잘못 사용하고 있는 경우인데
이런 상황에 대한 대표적인 예시는 바로 요청이 실패했을 때도
상태 코드를 성공의 의미로 200 OK를 보내는 것이다.
GET /v1/pepega_address
HTTP/1.1 200 OK
{ "success": false }
이렇게 설계한 API의 경우
위 예시처럼 HTTP 응답 바디에 요청의 성공/실패 여부나
실패 이유를 함께 담아서 보내주는 경우가 대다수인데
이렇게 되면 프론트엔드에서 처리가 조금 애매해질 수 있다.
서버로 보냈던 요청이 실패하면
서버는 반드시 400이나 500번대의 상태 코드를 보낼 것이고
그렇게 되면 fetch API는 에러를 발생시켜서 오류를 잡을 것이다.
하지만 200과 성공, 실패에 대한 내용을 보내게 되면
프론트엔드에서 if (!success) 구문을 추가해야하는 번거로움이 있다.
작업의 수행 상태를 알려주는 HTTP 상태 코드
클라이언트가 서버에게 작업을 요청하면
서버는 요청받은 작업을 수행한 후 작업의 수행 결과를 응답으로 보내주는데
이 때 HTTP 상태 코드를 사용하여 작업의 성공/실패 여부와
작업이 실패했다면 어떤 이유로 실패했는지 알려주게 된다.
HTTP 상태 코드는
200 = 성공
400 = 클라이언트가 잘못 요청함
500 = 서버가 잘못함
각 상황에 맞는 코드가 표준으로 정해져있다.
웹 상에서 돌아가는 기본적인 프로그램의 동작이나
프론트엔드, 백엔드 프레임워크들의 설계 또한 이 기준으로 만들어졌기 때문에
되도록 지키는게 좋다.
100번대
100번대 코드는 프로토콜을 교체해도 된다던가 계속 요청을 보내도 된다는 상태를 의미하지만
거의 만나본 적이 없다고 한다.
200번대
200번대 코드들은 클라이언트가 요청한 작업을 서버가 성공적으로 수행했다는 상태를 보여주는 코드다.
"요청한 작업이 성공" 이라는 응답만으로 만족시킬 수 있지만
더 디테일한 상태를 정의해야 하는 상황이라면
200번대의 상태 코드를 적극적으로 활용하면 된다.
200 OK
작업이 성공했음을 의미한다.
201 Created
작업이 성공되었고 리소스가 새롭게 생성되었다는 것을 의미한다. (회원가입)
204 No Content
작업이 성공되었고 컨텐츠 또한 더 이상 깔끔하게 존재하지 않음을 알린다. (게시글 삭제)
300번대
리다이렉션에 관련된 상태를 의미한다.
클라이언트가 요청한 리소스가 옮겨졌거나 삭제됐거나 해서
정상적인 방법으로는 더 이상 해당 리소스에 접근할 수 없고 다른 URL을 통해서 그 리소스에 접근해야 하는 경우
서버는 "여기에 주소 리소스 있음" 이라는 정보를 줄 수 있는데
이 때 사용되는 상태 코드들이 바로 300번대 코드들이다.
301 Moved Permanetly
301 Redirect 라는 별칭으로 불릴 만큼 리다이렉션을 위한 중요 코드 중 하나다.
브라우저는 자신의 요청의 응답으로 301을 받으면
HTTP 헤더에 있는 Location 필드를 찾고
해당 필드가 존재할 경우 Location 필드에 담긴 URL로 자동 리다이렉션한다.
HTTP/1.1 301 MovedPermanetly
Location: https://www.youtube.com/playlist?list=PLoF8DwY4sqhEcVl7CJAXJnlA48M_WZiUU
또한 구글과 같은 검색 엔진의 봇들은 특정 페이지에 접근했는데
응답으로 301 상태 코드를 받을 경우 자동으로 페이지 정보를 갱신하기도 하기 때문에
SEO(Search Engine Optimization) 관점에서도 이 상태 코드를 올바르게 사용하는 것은 매우 중요하다.
이런 리다이렉션 설정은 보통 서버 엔진의 설정 파일 내에서도 할 수 있고
백엔드 애플리케이션 내에서 직접 할 수도 있다.
일반적인 경우 HTTP 프로토콜로 접속한 사용자를 HTTPS 프로토콜을 사용해야만
접근 가능한 포트로 보내버릴 때에 많이 사용된다.
port 80 접속 -> port 433 리다이렉트
304 Not Modified
클라이언트가 요청한 리소스가 이전 요청과 비교하였을 때 전혀 달라진 점이 없는 것을 의미한다.
서버가 응답으로 이 상태 코드를 보내주면 클라이언트는 굳이 서버에게 리소스를 재전송 받아야 할 필요가 없다.
자신이 캐싱해놓았던 리소스를 사용하게 되며
이 과정에서 불필요한 통신 페이로드 낭비를 줄일 수 있다.
이 과정에서 클라이언트는 서버로부터 요청된 리소스를 받는 것이 아니라
자신이 캐싱해놓았던 리소스를 사용하는 것이므로 이 또한 캐싱된 리소스로 리다이렉션 되었다고 치는 것이다.
그런 이유로 304 상태 코드는 암묵적인 리다이렉션으로 불리기도 한다.
브라우저 역시 이 응답을 위한 자체 캐싱 기능이 있다.
304 상태 코드를 응답으로 받았는데 캐싱된 리소스가 없을 경우에는
빈 화면을 띄우거나 에러 화면이 노출된다.
그러니 이런 상황을 만나면
브라우저에 Cached Resource가 없는 것이 아닌가 에 대한 의심을 해볼 수 있다.
400번대
400번대 코드는 클라이언트가 서버에게 보낸 요청이 잘못된 경우를 의미한다.
400 Bad Request
"클라이언트가 요청을 잘못 날림"을 의미한다.
이 때 뭘 어떻게 잘못 날렸는지는 보통 HTTP 응답 바디에 담아서 알려주는 경우도 있지만
그렇지 않은 경우 백엔드 애플리케이션의 로그를 확인해야 한다.
401 Unauthrized
인증되지 않은 사용자가 인증이 필요한 리소스를 요청할 경우
인증이 필요하다고 알려주는 상태 코드다.
보통 로그인이 필요한 API를 비로그인 사용자가 호출했을 때 사용된다.
클라이언트가 401을 받을 경우
로그인이 필요하다는 것으로 판단하고
로그인 페이지로 사용자를 리다이렉션 하기도 한다.
403 Forbidden
클라이언트가 접근이 금지된 리소스를 요청했음을 의미한다.
401은 인증이 되지 않은 사용자가 접근했을 때고
403은 누구인지 신경쓰지 않고 이 리소스 요청은 금지 라는 뜻이다.
HTTPS 프로토콜로만 접근해야하는 리소스에
HTTP 프로토콜을 사용하여 접근했을 경우 403을 보내기도 한다.
404 Not Found
요청한 리소스가 존재하지 않는다는 뜻이다.
405 Method Not Allowed
현재 리소스에 맞지 않는 메서드를 사용했음을 의미한다.
백엔드 프레임워크의 경우 특정 컨트롤러에 해당 메서드를 사용하는 로직이 없다면
자동으로 405를 내려주기도 한다.
406 No Acceptable
서버 주도 컨텐츠 협상 을 진행했음에도 불구하고
알맞은 컨텐츠 타입이 없다는 것을 의미
클라이언트가 서버에 리소스를 요청할 때
HTTP 헤더의 Accept 필드를 사용하여 어떤 컨텐츠 타입의 리소스를 원하는지 얘기한다.
이 필드를 명시하지 않을 경우 브라우저는 자동으로 text/html 을 비롯한 몇 가지 타입을 스스로 정의하여 헤더에 담는다.
GET https://blog.naver.com/pepega
Accept text/html,application/xhtml+xml,application/xml,*/*
...
서버는 클라이언트가 보낸 요청의 Accept 필드를 보고
앞에서부터 하나씩 찾아가며 요청받은 리소스와 알맞은 컨텐츠 타입이 있는지 하나씩 살펴보게 된다.
이후 알맞은 컨텐츠 타입이 있다면 HTTP 응답 헤더의 Content-Type 필드에 해당 컨텐츠 타입을 명시해주게 된다.
HTTP/1.1 200 OK
Content-Type: text/html
이 과정에서 어떤 컨텐츠 타입의 리소스를 응답으로 내려줄 것인지는
전적으로 서버가 결정하게 된다.
이 과정을 "서버 주도 컨텐츠 협상" 이라고 한다.
위 예시에서
클라이언트가 받기를 원했던 타입 중 첫 번째 우선순위를 가진 text/html 을 받아왔지만
text/html 리소스가 없는 경우
application/xhtml+xml 부터 차례대로 탐색하게 된다.
끝까지 없는 경우 */* 와일드 카드에 걸리기 때문에
서버는 리소스가 어떤 컨텐츠 타입인지 상관하지 않고 그대로 응답해줄 것이다.
만약 모두 탐색했는데도 알맞은 리소스가 없으면
406 상태 코드와 함께 "원하는 컨텐츠 타입의 리소스가 없다" 라는 응답을 줄 것이다.
408 Request Timeout
클라이언트와 서버의 연결은 성사되었지만
요청의 본문이 계속 서버에 도착하지 않은 상황을 의미한다.
HTTP프로토콜을 활용하여 통신할 때는 반드시 클라이언트와 서버 간의 연결을 생성하고
요청 본문에 해당하는 데이터를 전송하게 되는데
408 상태 코드는 연결은 제대로 성사되었지만
서버가 아무리 기다려도 클라이언트가 보냈던 요청 본문을 받지 못하는 경우가 발생하게 된다.
429 Too Many Requests
클라이언트가 서버에 너무 많은 요청을 보낼 때 발생한다.
짧은 시간 안에 빠르게 요청을 마구 날려서 천천히 보내달라는 서버의 신호일 수 있고
유료 API를 사용하는 경우 돈을 더 내라는 의미로 사용되기도 한다.
서버에서는 429 상태 코드와 함께
응답 헤더의 Retry-After 라는 필드를 사용하여
"이 시간 이후 재요청 해주세요"라는 의미를 전달할 수도 있다.
500번대
클라이언트가 아닌 서버에서 문제가 생긴 경우다.
500 Internal Server Error
백엔드 애플리케이션 내에서 알 수 없는 에러가 발생했다는 의미이다.
대부분 제대로 핸들링되지 않은 에러가 발생한 경우가 많다.
에러의 원인을 클라이언트에게 알려주지 않는다.
핸들링되지 않는 에러의 원인을 클라이언트에게 고스란히 알려주는 것은
보안 사고가 발생할 가능성이 있다.
500 상태코드로 에러의 발생 자체만 알려주는 경우가 대부분이다.
만약 이 상태 코드를 만난다면 바로 서버로그를 보던가 Sentry, Bugsang과 같은
에러 모니터링 솔루션을 적극 활용하는 것을 추천한다.
502 Bad Gateway
502을 만나는 가장 흔한 상황은 백엔드 애플리케이션이 죽은 상황이다.
근데 왜 Server Died가 아닌 Bad Gateway와 같은 메시지일까?
백엔드 아키텍처가 아무리 간단한 구조라고 해도
절대 1개의 애플리케이션으로 구성되지 않기 때문이다.
여기서 말하는 게이트웨이는 애플리케이션 간의 추상적인 연결점을 의미한다.
메시지가 의미하듯 백엔드 아키텍처는 최소 2개 이상의 애플리케이션으로 구성된 경우가 대부분이다.
일반적인 경우 클라이언트가 보낸 요청은 바로 백엔드 애플리케이션으로 가지 않는다.
그 앞단에는 apache, nginx 같은 서버 엔진이나 로드밸런서와 같은 것이 대신 요청을 받아서
백엔드 애플리케이션에 전달해주는 경우가 대부분이다.
server {
listen 80;
server_name evan.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Nginx는 80번 포트에서 대기하다가
HTTP 프로토콜을 사용한 요청을 받아 3000번 포트에서 대기하고 있는 백엔드 애플리케이션에게 전달해주는 역할을 한다.
보안과 처리 효율 때문에 위와 같은 방법을 사용한다.
백엔드 앞 단에는 아예 프록시 서버를 두어서 문지기 역할을 시키는 것이다.
이 때 프록시 서버와 백엔드 애플리케이션 간의 연결된 추상적인 통로를 "게이트웨이" 라고 한다.
백엔드 애플리케이션이 죽어버린 경우
앞단의 문지기인 프록시 서버는 백엔드로부터 아무런 응답을 받지 못하게 되고
클라이언트에게 502 Bad Gateway 라는 응답을 보내주는 것이다.
503 Service Unavailable
서버가 요청을 처리할 준비가 되지 않았음을 의미한다.
502와 비슷한 느낌으로 사용되지만 503은 "일시적인 상황"을 의미하는 상태 코드다.
일반적으로 서버에 부하가 심해서 현재 요청을 핸들링 할 수 있는 여유가 없는 경우 많이 사용된다.
AWS Lambda에서는 요청을 처리할 때
컨테이너의 동시 실행 갯수를 초과할 정도의 리소스가 필요하거나
어떤 작업의 처리 시간이 Lambda에 설정된 컨테이너의 최대 수명 기간을 초과했을 경우 생기기도 한다.
503은 일시적인 상황이므로 429 Too Many Requests와 동일하게
응답 헤더에 Retry-After 필드를 사용하여 "이 시간 이후에 다시 요청"이라는 의미를 클라이언트에게 줄 수 있다.
504 Gateway Timeout
504는 408 Request Timeout과 마찬가지로 요청에 대한 타임아웃을 의미한다.
그러나 504 상태 코드는 클라이언트에서 보낸 요청 때문에 타임아웃이 발생하는 것이 아니라
백엔드 아키텍처 내부에서 서버끼리 주고 받는 요청에서 발생한다.
백엔드의 아키텍처는 단순이 백엔드 애플리케이션 하나로 구성된 것이 아니기 때문에
클라이언트의 요청이 서버에 닿은 뒤에도 백엔드 애플리케이션끼리의 통신이 발생하게 된다.
만약 프록시 서버 역할을 맡은 Nginx가 백엔드 애플리케이션에 클라이언트의 요청을 전달했는데
백엔드 애플리케이션이 일정 시간 응답하지 않을 경우 Nginx는 클라이언트에게
504 Gateway Timeout을 내려주게 되는 것이다.
출처
https://evan-moon.github.io/2020/03/15/about-http-status-code/