[강의] 모든 개발자를 위한 HTTP 웹 기본 지식 06-08 HTTP 상태코드, HTTP 헤더
모든 개발자를 위한 HTTP 웹 기본 지식 강의
김영한님의 인프런 강의(모든 개발자를 위한 HTTP 웹 기본 지식) 을 수강하면서 강의 내용을 일부 발췌해 요약한 글.
섹션 6 HTTP 상태코드
상태코드 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능
- 1xx (Informational): 요청이 수신되어 처리중 (거의 사용되지 않음)
- 2xx (Successful): 요청 정상 처리
- 3xx (Redirection): 요청을 완료하려면 추가 행동이 필요.
- 4xx (Client Error): 클라이언트 오류, 잘못된 문법 등으로 서버가 요청을 수행할 수 없음
- 5xx (Server Error): 서버 오류, 서버가 정상 요청을 처리하지 못함
만약 클라이언트가 인식할 수 없는 상태코드를 서버가 반환하는 경우에는 위의 상위 상태코드로 해석해서 처리하면 된다.
2xx
- 200 OK 요청 성공
- 201 Created 요청 성공 - 새로운 리소스가 생성됨
- 202 Accepted 요청이 접수되었으나 처리가 완료되지 않았음
- 204 No Content 요청 성공 - 응답 페이로드 본문에 보낼 데이터가 없음. save 버튼 같은 경우에 사용됨
3xx 리다이렉션
웹 브라우저는 3xx 응답 결과에 Location 헤더가 있으면 Location 위치로 자동 이동(리다이렉트)한다.
- 300 Multiple Choices
- 301 Moved Permanently
- 302 Found
- 303 See Other
- 304 Not Modified
- 307 Temporary Redirect
리다이렉션의 종류는 다음과 같다.
영구 리다이렉션:
- 특정 리소스의 URI가 영구적으로 이동해 원래의 URL을 사용할 수 없다. 검색 엔진 등에서도 변경을 인지할 수 있다. ex) /event -> /new-event
- 301 Moved Permanently 리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있다.
- 308 Permanent Redirect 301과 기능은 같으나 리다이렉트시 요청 메서드와 본문이 유지된다.
일시 리다이렉션:
- 일시적인 변경이므로 검색 엔진 등에서 URL을 변경하면 안된다. ex) 주문 완료 후 주문 내역 화면으로 이동하는 것
- 302 Found 리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있다. (대부분, 모호)
- 307 Temporary Redirect 302와 기능은 같으나 리다이렉트시 요청 메서도와 본문이 유지된다. (요청 메서드를 변경하면 안된다!)
- 303 See Other 302와 기능은 같으나 리다이렉트시 요청 메서드가 GET으로 변경된다. (명확히!)
- PRG(Post/Redirect/Get)
- POST로 주문 후에 웹브라우저를 새로고침하면 중복 주문이 될 수 있다. 이를 방지하기 위해 주문 후에는 주문 결과 화면을 GET 메서드로 리다이렉트하면 새로고침 하더라도 GET으로 주문 결과 화면이 새로고침된다.
- 모호한 302보다는 303, 307을 권장하나, 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용하므로 자동 리다이렉션시에 GET으로 변해도 된다면 302를 사용해도 무관하다.
특수 리다이렉션: 결과 대신 캐시를 사용
- 300 Multiple Choices 잘 안씀
- 304 Not Modified 캐시를 목적으로 사용하며, 클라이언트에게 리소스가 수정되지 않아 캐시를 재사용해도 된다는 것을 알려준다.(캐시로 리다이렉트한다.) 로컬 캐시를 사용해야 하므로 응답에 메시지 바디를 포함하면 안된다.
4xx 클라이언트 오류
오류의 원인이 클라이언트에 있는 경우로, 클라이언트가 이미 잘못된 요청, 데이터를 보내고 있기 때문에 똑같은 재시도는 실패한다. (5xx와의 차이)
- 400 Bad Request 클라이언트의 잘못된 요청(구문, 메시지 오류 등)으로 서버가 요청을 처리할 수 없는 경우. 클라이언트는 내용 재검토 후 요청해야함.
- 401 Unauthorized 해당 리소스에 인증되지 않은 경우. 오류 발생시 응답에 WWW-Authenticate 헤더와 함께 인증 방법을 설명한다. 참고로 인증은(Authentication)은 로그인과 같이 본인이 누구인지 확인하는 것이며 인가(Authorization)은 권한 부여로 인증이 있어야 인가가 있다.
- 403 Forbidden 서버가 요청을 이해했지만 승인을 거부하는 경우로, 어드민 등급이 아닌 사용자가 로그인은 했으나 어드민 등급의 리소스에 접근하려는 것처럼 인증 자격 증명은 있지만 권한이 불충분한 경우
- 404 Not Found 요청 리소스가 서버에 없어 찾을 수 없는 경우. 또는 클라이언트가 권한이 부족한 리소스에 접근할 경우 리소스를 숨기고 싶을 때 사용
5xx 서버 오류
서버 문제로 오류가 발생하는 경우로 재시도시 성공할 수도 있다.
- 500 Internal Server Error 서버 내부 문제로 오류가 발생하는 경우로 모호한 경우 대부분 500 오류를 사용
- 503 Service Unavailable 서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없는 경우, Retry-After 헤더 필드로 얼마 뒤에 복구되는지 보낼 수도 있음
웬만하면 5xx 에러를 만들면 안 된다! 서버에 정말 문제가 있을 때만 5xx 에러
섹션 7 HTTP 헤더1 - 일반 헤더
header-field = field=name(대소문자 구분X) “:” OWS(띄어쓰기 허용) field-value OWS
HTTP 헤더는 HTTP 전송에 필요한 모든 부가정보를 담는 용도로 메시지 바디의 내용, 크기, 압축, 인증. 요청 클라이언트, 서버 정보 등 무수한 정보가 들어가며, 표준 헤더 필드가 굉장히 많다. 과거 RFC2616 버전(1999)에서는 헤더를 General(메시지 전체), Request(요청 정보), Response(응답 정보), Entity(엔티티 바디 정보) 의 네 개로 분류하여 메시지 본문은 요청이나 응답에서 전달할 실제 데이터인 엔티티 본문을 전달하는데 사용하고, 엔티티 헤더는 엔티티 본문의 데이터를 해석할 수 있는 데이터 유형, 길이 등의 정보를 제공하는 식이었다. 그러나 RFC7230~7235 버전(2014)이 등장하면서 엔티티라는 용어 대신 표현 메타데이터와 표현 데이터의 합인 표현(Representation) 이라는 용어를 사용하게 되었으며, 메시지 본문(message body, payload)을 통해 표현 데이터를 전달하고 표현 헤더는 표현 데이터를 해석할 수 있는 정보를 제공하는 식으로 개정되었다.
표현
- Content-Type: 표현 데이터의 형식, 미디어 타입과 문자 인코딩에 관련된 정보.
- Content-Encoding: 표현 데이터의 압축 방식, 데이터를 전달하는 곳에서 압축 후 인코딩 헤더를 추가하면 데이터를 읽는 쪽에서 인코딩 헤더의 정보로 압축 해제.
- Content-Language: 표현 데이터의 자연 언어 ex) ko, en, en-US …
- Content-Length: 표현 데이터의 길이(바이트 단위), 참고로 Transfer-Encoding의 경우 이미 정보가 들어가 있으므로 Content-Length를 사용하면 안됨
표현 헤더는 전송, 응답 둘다 사용할 수 있다.
협상과 우선 순위
협상(Content Negotiation) 클라이언트가 선호하는 표현을 요청하는 것으로, 요청시에만 사용한다.
- Accept: 클라이언트가 선호하는 미디어 타입 전달
- Accept-Charset: 클라이언트가 선호하는 문자 인코딩
- Accept-Encoding: 클라이언트가 선호하는 압축 인코딩
- Accept-Language: 클라이언트가 선호하는 자연 언어
우선 순위
- Quality Values(q)값을 사용한다.
- q는 0~1 사이의 값으로 클수록 높은 우선순위를 가지며, 1인 경우 생략 가능하다.
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7 에서 우선순위는 1. ko-KR(q=1이 생략되어 있다.), 2. ko, 3.en-US, 4.en 이다.
- 구체적인 것이 우선한다.
Accept: text/*, text/plain, text/plaing;format=flowed, */에서 우선순위는 1. text/plain;format=flowed, 2. text/plain, 3. text/, 4. */이다.
전송 방식
- 단순 전송 Content-Length
- 압축 전송 Content-Encoding
- 분할 전송 Transfer-Encoding. Chunk(덩어리)로 나누어 분할하는 방식으로 Content-Length가 예상이 되지 않고 Chunk 마다 길이가 있으므로 Content-Length 전송 불가
- 범위 전송 Range, Content-Range 범위를 지정해 요청 가능
정보
일반 정보
- From 유저 에이전트의 이메일 정보로 검색 엔진에서 주로 사용되며 일반적으로 잘 사용되지는 않는다. 요청에서 사용
- Referer 현재 요청된 페이지의 이전 웹 페이지 주소로 이를 통해 유입 경로 분석이 가능하다. 요청에서 사용
- User-Agent 유저 에이전트의 애플리케이션 정보(클라이언트의 애플리케이션 정보)로 어떤 종류의 브라우저에서 장애가 발생하는지 파싱하여 파악이 가능하다. 요청에서 사용
- Server 요청을 처리하는 ORIGIN 서버(진짜 나의 요청이 있는 찐서버)의 소프트웨어 정보로 응답에서 사용
- Date 메시지가 발생한 날짜와 시간으로 응답에서 사용
특수한 정보
- Host 요청한 호스트 정보(도메인)로 필수값이며, 하나의 서버가 여러 도메인을 처리해야 할 때나 하나의 IP 주소에 여러 도메인이 적용되어 있을 때 Host 정보가 판단 근거가 되므로 매우 중요하다.
- Location 페이지 리다이렉션, 응답코드 3xx에서 설명한 내용으로 201(Created)의 경우 Location은 요청에 의해 생성된 리소스 URI이고 3xx(Redirection)의 경우 Location값은 요청을 자동으로 리디렉션하기 위한 대상 리소스를 가리킨다.
- Allow 허용 가능한 HTTP 메서드로, 405 (Method Not Allowed) 에서 응답에 포함해야 한다. 참고만 하자
- Retry-After 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간으로 503 (Service Unavailable) 에서 서비스가 언제까지 불능인지를 알려줄 수 있다. 날짜 표기 방식과 초단위 표기 방식이 있다.
인증
- Authorization 클라이언트 인증 정보를 서버에 전달
- WWW-Authenticate 리소스 접근시 필요한 인증 방법 정의. 문제시 401 (Unauthorized) 응답과 함께 사용된다.
쿠키
HTTP는 Stateless이므로 로그인을 하더라도 클라이언트와 서버는 서로 상태 유지를 하지 않으므로 로그인 정보가 소실된다. 그러나 이를 위해 모든 요청과 링크에 사용자 정보를 포함하는 것은 보안과 더불어 여러 가지 심각한 문제가 발생할 수 있다. 이러한 문제에 대안으로 나온 것이 쿠키이다. 쿠키의 주사용처는 사용자 로그인 세션 관리, 광고 정보 트래킹이 있는데, 정보는 웹 브라우저의 쿠키 저장소에 저장되고 웹브라우저는 해당 서버에 요청을 보낼 때마다 쿠키 저장소의 쿠키를 확인 후 서버에 전송한다.(클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청시 서버로 전달) 모든 요청에 쿠키 정보는 자동으로 포함된다. 마찬가지로 모든 곳에 쿠키 정보를 보내면 보안 문제나 네트워크 트래픽 추가가 유발되는 문제 등이 발생할 수 있다. 따라서 세션 id, 인증 토큰 등 최소한의 정보만 사용하는 것이 바람직하며 보안에 민감한 데이터는 저장하면 안된다. 쿠키를 제약하는 여러 방법이 존재한다. 참고로 서버에 전송하지 않고 웹 브라우저 내부에 데이터를 저장하고 싶은 경우에는 웹 스토리지가 있다.
Set-Cookie 서버에서 클라이언트로 쿠키 전달(응답)
set-cookie: sessionId= abcde1234; expires= Sat, 26-Dec-2020 00:00:00 GMT; path=/; domain=.google.com; Secure
생명주기(Expires, max-age): 쿠키는 생명주기에 따라 브라우저 종료시까지만 유지되는 세션 쿠키와 만료 날짜까지 유지되는 영속 쿠키로 나눌 수 있다. expires는 해당 만료일이 되면 쿠키가 삭제되며 GMT 기준으로 날짜를 넣어주어야 한다. max-age의 경우 초단위의 세팅이며 0이나 음수를 지정하면 쿠키가 삭제된다.
도메인(Domain): 도메인은 명시하거나 생략할 수 있다. 명시의 경우 명시한 문서 기준 도메인과 서브 도메인을 포함하나, 생략하는 경우에는 현재 문서 기준에만 적용되며 하위 도메인에서는 쿠키 접근이 불가하다.
경로(Path): 경로를 포함ㅎ나 하위 경로 페이지만 쿠키 접근이 가능하다. 일반적으로 path=/ 루트로 지정한다.
보안(Secure): 쿠키는 http,https를 구분하지 않고 전송하는데, secure를 적용하면 https인 경우에만 전송한다. HttpOnly는 XSS 공격을 방지하기 위한 것으로 자바스크립트에서 쿠키 접근이 불가하고 HTTP 전송에만 사용 가능하다. SameSite는 XSRF 공격 방지를 위한 것으로 요청 도메인과 설정 도메인이 같은 경우에만 쿠키 전송이 가능하다.
섹션 8 HTTP 헤더2 - 캐시와 조건부 요청
캐시
데이터를 다운받는 요청을 반복한다고 가정해보자. 데이터가 변경되지 않아도 계속 네트워크를 통해 같은 데이터를 다운로드 받아야 하는데, 인터넷 네트워크는 매우 느리고 비싸며 브라우저 로딩 속도 또한 느려진다. 캐시를 적용하면, 최초 요청시에는 웹 브라우저 내 캐시 저장소에 응답 결과와 유효 시간(cache-control)을 포함한 캐시를 저장하고, 두번째 요청시에는 캐시 유효 시간 검증(유효 시간 내에 있는가) 후 캐시에서 조회할 수 있게 된다. 즉. 캐시 가능 시간동안 네트워크를 사용하지 않아도 되며 비싼 네트워크 사용량을 줄이고 브라우저 로딩 속도 또한 향상시킬 수 있다.
검증 헤더와 조건부 요청
앞선 설명에 이어, 만약 캐시 시간이 초과된 경우에는 다시 네트워크 다운로드가 발생하며 캐시를 재요청하고 갱신하게 되는데 이때 다음의 두 가지 상황이 나타난다.
- 서버에서 기존 데이터를 변경함
- 서버에서 기존 데이터를 변경하지 않음
2번의 경우에는 데이터를 전송하는 것보다 저장해두었던 로컬 캐시를 재사용하는 것이 경제적이다. 단, 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있어야 하는데 이때 필요한 것이 검증 헤더 이다.
검증 헤더
캐시 데이터와 서버 데이터가 같은지 검증하는 데이터로 Last-Modified와 ETag가 있다. 이 두가지를 활용해 클라이언트에서 서버로 조건부 헤더를 만들어 요청할 수 있다.
Last-Modified (데이터가 마지막에 수정된 시간)
웹 브라우저에서 요청한 값의 if-modified-since (이 이후에 데이터가 수정되었나?)와 캐시의 Last-Modified 를 비교한다. 수정된 경우 200 (OK)로 Body를 포함한 모든 데이터를 전송하는 반면, 수정이 되지 않은 경우 HTTP Body가 없는 304 (Not Modified)로 응답한다. 클라이언트는 304 이므로 헤더 정보로 캐시의 메타 정보를 갱신한 뒤 캐시를 재사용할 수 있게 되는데 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드하므로 매우 실용적인 해결책이라고 할 수 있다.
그러나 1초 미만 단위로는 캐시 조정이 불가능하고 날짜 기반의 로직을 사용하므로 a->b->a 로 수정해 데이터 결과 자체는 같으나 날짜가 다른 경우에도 전체 데이터를 다시 다운로드해야 한다는 문제가 있다. 또한 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고자 하는 경우와 같이, 서버에서 별도의 캐시 로직을 관리하고 싶은 경우에 적용이 어렵다.
이때 사용할 수 있는 것이 ETag이다.
ETag
ETag(Entity Tag)는 캐시용 데이터에 임의의 고유한 버전 이름을 달아두는 것으로, 단순히 if-None-Match 와 ETag 가 같으면 유지하고 다르면 다시 받는 식으로 사용할 수 있다. 데이터가 변경되면 이름을 변경해 Hash를 다시 생성한다. 클라이언트는 단순히 값을 서버에 제공할 뿐 캐시 메커니즘을 모르고, 캐시 제어 로직을 서버에서 완전히 관리한다.
조건부 요청 헤더
- If-Match, If-None-Match: ETag 값 사용
- If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용
캐시 제어 헤더
- Cache-Control: 캐시 제어
- 캐시 지시어(directives)
- max-age: 캐시 유효 시간, 초 단위
- no-cache: 데이터는 캐시해도 되지만, 항상 origin 서버에 검증하고 사용해야 함
- no-store: 데이터에 민감한 정보가 있으므로 저장하면 안되고 메모리에서 사용후 최대한 빨리 삭제해야 함
- 캐시 지시어(directives)
- Pragma: 캐시 제어(하위 호환) 거의 사용하지 않음
- Expires: 캐시 유효 기간(하위 호환) 거의 사용하지 않음. 보다 유연한 Cache-Control:max-age 권장
프록시 캐시
클라이언트와 원서버 사이에 존재하는 공용 캐시 서버(public 캐시).
- Cache-Control:
- 캐시 지시어(directives)
- public : 응답이 public 캐시에 저장돼도 됨
- private: 기본값. 응답이 해당 사용자만을 위한 것이므로 private 캐시에 저장해야 함
- s-maxage: 프록시 캐시에만 적용되는 max-age (참고)
- Age: origin 서버에서 응답 후 프록시 캐시 내에 머문 시간(초) (참고)
캐시 무효화
사용자의 통장 잔고 같이 캐시가 되면 안되는 페이지가 있다. 이때 확실한 캐시 무효화 응답은 다음과 같으며 전부 포함해야 한다.
- Cache-Control: no-cache, no-store, must-revalidate(캐시 만료 후 최초 조회시 원 서버에 검증해야 하고 접근 실패시 반드시 504 Gateway Timeout 오류가 발생해야 함)
Pragma: no-cache (하위 호환)
- no-cache는 항상 원서버에 검증하고 사용하는데 굳이 must-revalidate까지 해야 할까? no-cache에서는 순간 네트워크 단절 등 원 서버에 접근 불가한 경우 서버 설정에 따라 캐시 데이터를 반환할 수도 있다. (오류보다는 오래된 데이터라도 보내주자라는 마인드) 그러나 must-revalidate는 원 서버에 접근 불가한 경우 무조건 오류가 발생하도록 하여 민감할 수 있는 과거 데이터를 반환하지 않도록 한다.
++
HTTP에 대해 더 깊이 있게 학습하고자 한다면 HTTP 스펙 을 참고하자.
드디어 HTTP 강의도 완강했다! 지연이가 추천해 준 그림으로 배우는 HTTP & Network 를 읽으면서 복습해봐야겠다.