HTTP(Hyper Text Trasfer Protocol)

API URI를 설계할 때는 리소스와 행위를 분리해서 URI는 리소스만 식별하자. 즉 URI는 리소스를 판별할 때만 사용해야 하지, create, update 같은 행위는 들어가면 안 된다. 이 행위는 HTTP Method(GET, POST, DELETE, PUT, PATCH …)가 대신한다.

PUT: 리소스를 대체

  • (수정을 할 때는 웬만하면 PATCH를 사용하자! 하지만 게시글 수정 같은 경우는 PUT으로 해도 괜찮을 듯)
  • 리소스가 있으면 대체
  • 리소스가 없으면 생성: 근데 스프링으로 생각하면 PutMapping으로 받아도 내부 로직은 내가 정하는 거 아닌가?
  • 쉽게 이야기 해서 덮어버림
  • Post와 차이점은, Put의 리소스의 정확한 위치를 알고 있어야

POST는 멱등(Idempotent)하지 않다!

단, idempotent는 외부 요인으로 중간에 리소스가 변경되는 것까지는 고려치 않는다. 예를 들어, get을 했는데, 두 번째로 get 하기 전에, 다른 유저가 put이나, patch로 데이터를 변경하고, get을 하면 데이터가 변경되겠으나, 이건 멱등성을 판단하는 데 고려 대상이 아니다.

캐시가능성(Cacheable)

  • 응답 결과 리소스를 캐시해서 사용해도 되는가?
  • GET, HEAD(GET의 바디부분 제거), POST, PATCH 캐시 가능
  • 실제로는 GET, HEAD 정도만 사용
  • POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데, GET은 URL만 키로 잡고 캐시하면 되는 반면, POST는 보내는 body 안에 데이터까지 캐시 키로 보유해야 하기 때문에 쉽지 않음.

만약 모르는 상태 코드가 나타나면?***

  • 클라이언트가 인식할 수 없는 상태 코드를 서버가 반환하면? ->클라이언트는 상위 상태코드로 해석해서 처리(미래에 새로운 상태 코드가 추가되어도 클라이언트를 변경하지 않아도 됨)
    • 299 ??? -> 2xx (Successful)
    • 451 ??? -> 4xx (Client Error)
    • 599 ??? -> 5xx (Server Error)

HttpStatus Code

  • 1xx (Informational): 요청이 수신되어 처리중. 거의 사용되지 않는다.

  • 2xx (Successful): 클라이언트의 요청을 정상 처리.
    • 200 OK
    • 201 Created: 요청 성공해서 새로운 리소스가 생성됨(Location header가 있을 수도 있음)
    • 202 Accepted: 요청이 접수되었으나 처리가 완료되지 않음. 배치 처리 같은 곳에서 사용한다. 예를 들어, 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리함. 잘 사용 안 함
    • 204 No Content: 서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없다. 예를 들어, 웹 브라우저 편집기에서 저장 버튼을 누르면 데이터가 POST로 서버로 넘어가서 해당 문서를 저장하고, 되돌려줄 응답 데이터가 있을까? 본문을 받아봐야 할 게 없음. 결과 내용이 없어도 204(2xx) 메시지만으로 성공으로 간주할 수 있다.
  • 3xx (Redirection): 요청을 완료하려면 클라이언트 프로그램(웹 브라우저)의 추가 행동이 필요. 웹 브라우저는 3xx 응답 결과에 Location header가 있으면, Location 위치로 자동 이동(Redirect)
    • 300 Multiple Choices: 안 씀. 잊어버려~
    • 301 Moved Permanently: 경로가 바뀌면, 301을 뱉고, Location header 필드에 /new-page를 주면, 웹 브라우저가 새로운 경로로 서버로 재요청
    • 302 Found: 리다이렉트 시 요청 메서드가 GET으로 변할 수도 있고, 본문(기존 Body 데이터)이 제거될 수 있음. 브라우저마다 될 수도 있고 안 될 수도 있지만 대부분 변함. 그래서 메서드가 변하면 안 되면 307을 사용하고, 메서드가 GET으로 확실하게 변해야 하면 307을 사용하자.
    • 303 See Other: 302와 기능은 같지만 리다이렉트 시 요청 메서드가 무조건 GET으로 변경됨.
    • 304 Not Modified: 캐시를 목적으로 사용. 클라이언트에게 리소스가 수정되지 않았음을 알려준다. 따라서 클라이언트는 로컬 PC에 저장된 캐시를 재사용한다. 캐시로 리다이렉트. 304 응답은 로컬 캐시를 사용하므로 메시지 바디를 포함하면 안 된다.
    • 307 Temporary Redirect: 302와 기능은 같지만, 리다이렉트 시 요청 메서드와 본문 유지해야 함(요청 메서드와 body를 유지해야 한다.)
    • 308 Permanent Redirect:
    • PRG(Post/Redirect/Get): 만약에 웹 브라우저에서 POST 요청을 통해서 주문을 보냈는데 그 상태에서 새로고침은 한다면? 그럼 POST가 한 번 더 나가서 중복 주문이 될 수 있다. 그래서 이런 상황에서는 사용자가 새로고침 하는 걸 대비해서, POST 후에 주문 결과 화면은 GET 메서드로 Redirect(Location header와 함께 303을 전달)를 할 필요가 있다. 그러면 새로 고침을 해도 결과 화면인 GET을 조회(하지만 원칙적으로는 똑같은 메서드가 나가더라도 서버에서 주문 번호 등을 통해서 2차적으로 막아야 한다.)
  • 4xx (Client Error): 클라이언트 오류, 잘못된 문법 등으로 서버가 요청을 수행할 수 없음.
    • 400 Bad Reqeust: 요청 구문, 메시지 등등 오류, 클라이언트는 요청 내용을 다시 검토하고, 보내야 한다. 요청 파라미터가 잘못되었거나, API 스펙이 맞지 않을 때 발생.
    • 401 Unauthorized: 인증(Authentication)이 되지 않음.
      • 인증(Authentication): 본인이 누구인지 확인(로그인)
      • 인가(Authorized): 권한 부여 (Admin 권한처럼 특정 리소스에 접근할 수 있는 권한, 인증이 있어야 인가가 있음)
    • 403 Forbidden: 서버가 요청을 이해했지만 승인이 거부됨. 즉, 누구인지는 확인됐지만(인증) 해당 리소스에 접근 권한이 없음(인가). 예를 들어, 일반 User가 Admin 페이지에 들어갈 수는 없다.
    • 404 Not Fount: 요청 리소스를 찾을 수 없음. 즉, 요청 리소스가 서버에 없거나 클라이언트가 권한이 부족한 리소스에 접근할 때 해당 리소스의 존재 유무를 숨기고 싶을 때 사용.
  • 5xx (Server Error): 서버 오류, 서버가 정상 요청을 처리하지 못함.
    • 503 Service Unavaliable: 서버가 일시적인 과부화 또는 예정된 작업으로 잠시 요청을 처리할 수 없음. Retry-After header 필드에 얼마 뒤에 복구되는지 예상 시간을 보낼 수 있음. 근데 대부분 장애는 예상 불가이므로 503을 보는 경우는 많이 없다.

HTTP header

General header: 모든 HTTP 통신(요청이든 응답이든)에 적용되는 정보 e.g. Connection: close Request header: 요청을 보낼 때 들어가는 헤더 e.g. User-Agent: Mozila/5.0 Response header: 응답을 보낼 때 들어가는 헤더 e.g. Server: Apache Entity header: 메시지 바디에 대한 메타 정보을 표현하는 헤더 e.g. Content-Type: text/html, Content-Length: 3423

HTTP/1.1 200 OK                        
Content-Type: text/html; charset=UTF-8 | <--------Representation Header
Content-Length: 3423                   |

<html>                 |   
      <body>...</body> | <---- Representation Data
    </html>            |

Representation header

  • 데이터 표현에 관한 header
  • Content-Type: 표현 데이터의 형식. e.g. text/html; charset=UTF-8, application/json, image/png …
  • Content-Encoding: 표현 데이터의 압축 방식. 데이터를 전달하는 곳에서 메시지 바디를 압축 후에 인코딩 헤더 추가하고 데이터를 읽는 쪽에서는 인코딩 헤더의 정보로 압축 해제. e.g. gzip, deflate, identity(압축 안 함)
  • Content-Language: 표현 데이터의 자연 언어 e.g. ko, en, en-US
  • Content-Lenght: 표현 데이터의 길이. Transfer-Encoding(전송 코딩)을 사용하면 Content-Length를 사용하면 안 됨. => 표현 데이터는 request, response 둘 다 사용

Content Negotitation header: 클라이언트가 선호하는 표현 요청(Negotitation header는 request에서만 사용)

  • Accept: 클라이언트가 원하는 표현으로 전달해라. e.g. application/json, text/html
  • Accept-Charset: 클라이언트가 원하는 문자 인코딩 방식
  • Accept-Encoding: 클라이언트가 원하는 압축 인코딩 방식
  • Accept-Language: 클라이언트가 선호하는 자연 언어. 하지만 서버 쪽에서 내가 원하는 언어를 지원하지 않으면? -> 이 때 협상(negotitation)이 필요하다.
    • Quality Values(q) 값 사용(0~1, 클수록 우선 순위가 높고, 생략하면 1로 간주)
    • e.g. Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7 (ko-KR은 생략되어 있으므로 값이 1)
  • 협상과 우선 순위는 구체적인 것이 우선한다.*
    • Accept: text/*, text/plain, text/plain;format=flowed
      1. text/plain;format=flowed
      2. text/plain
      3. text/*
      4. /

전송(Transfer) 방식

  • 단순 전송: 한 번에 요청하고 한 번에 모두 전송
    • Content-Length: 3423 -> 3423 byte의 데이터 한 번에 전송
  • 압축 전송: 데이터를 압축해서 전송하고 클라이언트는 Content-Encoding 필드를 보고 다시 디코딩
    • Content-Encoding: gzip
    • Content-Length: 521
  • 분할 전송: 데이터를 청크별로 분할해서 전송. 분할 전송을 하려면 Content-Length를 예측할 수 없으므로 Content-Length 필드를 포함시키면 안 된다.
    • Content-Type: text/plain
    • Trasfer-Encoding: chunked
  • 범위 전송: 만약에 전송 중에 중간에 끊기면 처음부터 다시 전송하라고 재요청하면 낭비니까 특정 범위부터 전송해달라고 할 수 있다.
    • Content-Type: text/plain
    • Content-Range: bytes 1001-2000 / 2000

Referer: 이전 웹 페이지 주소

  • 현재 요청된페이지의 이전 웹 페이지 주소
  • A -> B로 이동하는 경우 B를 요청할 때, Referar: A를 포함해서 요청
  • Referar를 사용해서 유입 경로 분석 가능
  • Request에서 사용
  • 참고: referer는 단어 referrer의 오타, 관습적으로 굳어짐

User-Agent: 유저 에이전트 애플리케이션 정보

  • user-agent: Mozila/5.0 (Macintosh; ~~~)
  • 클라이언트의 애플리케이션 정보(웹 브라우저 정보 등등)
  • 통계 정보
  • 어떤 종류의 브라우저에서 장애가 발생하는지 파악 가능
  • Request에서 사용
  • 서버 입장에서 굉장히 도움이 되는 정보. 예를 들어, 특정 브라우저에서 에러가 생긴다?

Server: Request를 처리하는 Origin Server의 소프트웨어 정보 (Request가 바로 Origin Server로 가는 게 아니라 여러 프록시 서버를 거친다)

  • Server: Apache/2.2.22 (Debian)
  • server: nginx
  • Response에서 사용

Date: 메시지가 발생한 날짜와 시간

  • Date: Tue, 15 Nov 1994 08:12:31 GMT
  • Response에서 사용

Host: 요청한 호스트 정보(도메인)

  • Request에서 사용
  • 필수 헤더
  • 하나의 서버가 여러 도메인을 처리해야 할 때, 하나의 IP 주소에 여러 도메인이 적용되었을 때
    • 예를 들어, 하나의 서버에 여러 대의 가상 서버가 있기 때문에 IP만 안다고 해서 어떤 도메인으로 요청을 했는지 알 수 없을 때, Host 정보를 통해서 해당 도메인에 요청 가능

Location: 페이지 리다이렉션

  • 웹 브라우저는 3xx의 응답 결과에 Location 헤더가 있으면, Location 위치로 자동 이동
  • 201(Created): Location 값은 요청에 의해 생성된 리소스 URI
  • 3xx(Redirection): Location 값은 요청을 자동으로 리다이렉션 하기 위한 리소스를 가리킨다.

Allow: 허용 가능한 HTTP 메서드

  • 405(Method Not Allowed)에서 응답에 포함돼야 함
  • Allow: GET, HEAD, PUT
  • 서버에서 잘 구현되는 스펙은 아님

Authorization: 클라이언트 인증 정보를 서버에 전달

  • Authorization: Basic xxxxxxxxx

WWW-Authenticate: 리소스 접근 시 필요한 인증 방법 정의

  • 리소스 접근 시 필요한 인증 방법 정의
  • 401 Unauthorized 응답과 함께 사용
  • WWW-Authenticate: Newauth realm=’apps’, type=1, title=”Login to "apps"”, Basic realm=”simple” <– 공식 스펙 예제
  • set-cookie: sessionId=adcde1234; expires=Sat, 26-Dec-2020 00:00:00 GMT; path=/; domain=.google.com; Secure
  • 사용처 예
    • 로그인 세션 관리 많이 사용됨
    • 광고 정보 트래킹
  • 쿠키 정보는 항상 서버에 전송 됨
    • 네트워크 트래픽 추가 유발
    • 최소한의 정보만 사용(sessionId, 인증 토큰)
    • 서버에 전송하지 않고, 웹 브라우저 내부에 데이터를 저장하고 싶으면 웹 스토리지(localStorage, sessionStorage) 참고
  • 생명 주기
    • set-cookie: expirest=Sat, 26-Dec-2020 04:39:21 GMT => 만료일이 되면 삭제.
    • set-cookie: max-age=3600 => 3600초 뒤에 삭제. 0이나 음수를 지정하면 쿠키 삭제
    • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료 때까지만 유지
    • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
  • 도메인: 쿠키에는 도메일을 지정할 수 있다. 생각하면 만들어진 쿠키가 아무 사이트에서 전송되면 큰일 나겠지?
    • e.g. domain=google.com
    • domain을 명시할 경우: 명시한 문서 기준 도메인 + 서브 도메인 포함 => domain=google.com은 물론이고 calendar.google.com도 쿠키 접근 가능
    • domain을 생략할 경우: 현재 문서 기준 도메인만 적용됨. 즉 서브 도메인은 접근 불가.
  • 경로(path): domain으로 1차 필터를 하고, 해당 경로 하위 경로 페이지만 쿠키 접근 가능
    • e.g. path=/members <== 그러면 members 하위 경로만 쿠키 사용 가능
  • 주의: 보안에 민감한 데이터(e.g. 주민번호, 신용카드 번호 등)은 저장하면 절대 안 됨!!
  • 보안(Secure, HttpOnly, SameSite)
    • Secure
      • 쿠키는 원래 http, https를 구분하지 않고 전송하지만 Secure를 추가하면 https인 경우에만 전송
    • HttpOnly: XSS 공격 방지
      • 자바스크립트에서 원래 쿠키를 접근할 수 있지만 HttpOnly를 추가하면 자바스크립트에서 해당 쿠키 접근 불가(document.cookie => X)
      • HTTP 전송에만 사용
    • SameSite: XSRF 공격 방지
      • 요청 도메인과 쿠키에 설정된 도메인이 같은 경우에만 쿠키 전송

캐시 시간 초과

  • 캐시 유효 시간이 초과해서 서버에 다시 요청하면 다음 두 가지 상황이 발생한다.
    1. 서버에서 기존 데이터를 변경함.
    2. 서버에서 기존 데이터를 변경하지 않음.
  • => 캐시 유효 시간이 초과해서 서버로 다시 요청은 했다만, 사실상 데이터가 바뀌지 않았다면? 굳이 처음부터 다시 다운로드 받아야 할까? (단, 클라이언트 데이터와 서버 데이터가 같다는 사실을 확인할 수 있는 방법이 필요)
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
**Last-Modified: 2020년 11월 10일 10:00:00**
Content-Length: 34012

위와 같은 HTTP 응답 값을 캐시에 저장하고, 60초 후에 해당 파일을 보내달라는 요청을 하면 우선적으로 cache-control 값을 확인 후 만료되지 않으면 로컬에 있는 캐시 파일을 쓰고, 만약에 만료가 되면 서버로 해당 파일에 다시 서버로 요청할 때 아래와 같이 요청한다.

GET/star.jpg
**if-modified-since: 2020년 11월 10일 10:00:00**

서버가 위 요청을 받으면 if-modified-since 값을 확인한 후, 파일이 수정되지 않았으면 다음과 같이 응답을 보낸다.

Http/1.1 304 Not Modified -> 3xx: redirection(너의 캐시로 리다이렉션해라)
Content-Type: image/jpeg
cache-control: max-age=60
Last-Modified: 2020년 11월 10일 10:00:00
Content-Length: 34012

위 요청을 보면 알 수 있는 게 Http Body가 없다. 즉 수정 이력이 없으면 클라이언트가 가지고 있는 캐시 데이터를 그대로 사용하면 된다. 이미지 데이터가 생각보다 크기 때문에 해당 데이터를 보내지 않은 것만 해도 네트워크 부하를 줄일 수 있다. 정리하자면,

  • 캐시 유효 시간이 초과해도, 서버의 데이터의 변경 이력이 없으면 304 Not Modified + 헤더 메타 정보만 응답(바디 데이터 X)
  • 클라이언트는 서버가 보맨 응답 헤더 정보로 캐시의 메타 정보를 갱신
  • 클라이언트는 캐시에 저장되어 있는 데이터 재활용
  • 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드 했으므로 네트워크 부하가 적다. (만약에 데이터가 변경되면 200 OK와 함께 바디에 이미지 데이터 담아서 클라이언트로 전송)

ETag(Entity Tag)

  • if-modified-since가 일치하지 않더라도 서버가 캐시를 컨트롤하고 싶을 경우 사용한다.
  • 캐시용 데이터에 임의의 고유한 버전 이름을 달아둔다.
    • e.g. ETag: “v1.0”, ETag: “a2jodkasd21” … etc
  • 데이터가 변경되면 이 이름을 바꾸어서 변경함(Hash를 다시 생성)
    • ETag: “aaaaa” -> ETag: “bbbbb”
  • 진짜 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받기.
Http/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
ETag: "aaaaa"
Content-Length: 34012

body: { asdadsasdsaasd  ....}
GET /star.jpg
if-none-match: "aaaaa"
HTTP/1.1 304 Not Modified
content-type: image/jpeg
cache-control: max-age=60
ETag: "aaaaa"
content-length: 34012

만약 위와 같이 ETag와 같이 요청이 오고 cache-control이 지정한 시간이 지나고 서버에 다시 해당 데이터를 재요청하면 ETag가 같으면 서버는 캐시 데이터를 쓰라고 응답이 온다. 즉 캐시 제어 로직을 완전히 서버에서 관리한다.

Cache-Control(캐시 지시어)

  • cache-control: public => 응답이 public 캐시(프록시 서버의 캐시, 예를 들면 유튜브에서 특정 영상에 1만 뷰 이상이 되면 해당 데이터를 그 나라 프록시 서버 캐시에 저장되는 매커니즘이라고 한다면, 해당 데이터는 public 속성이다)에 저장되어도 됨
  • cache-contol: private => 응답이 해당 사용자만을 위한 것임, private 캐시에 저장되어야 한다(default). 즉, 중간에 프록시 캐시에 저장되면 안 됨
  • cache-control: s-maxage => 프록시 캐시에만 적용되는 max-age
  • age: 60(HTTP header): origin 서버에서 응답 후 프록시 캐시 내에 머문 시간(sec)

캐시 무효화

캐시를 적용하지 않으면 무조건 캐시가 적용되지 않는 게 아니다. 웹 브라우저 자체적으로 캐시를 적용할 때가 많은데, 반드시 캐시가 적용되면 안 되는 데이터가 있을 수 있으므로 이럴 경우에는 cache-control: no-cache, no-store, must-revalidate + (과거 HTTP 1.0 하위 호환을 위해서) pragma: no-cache 옵션을 추가하도록 하자. 그러면 웹 브라우저도 해당 데이터는 캐시하지 않는다.

  • cache-control: no-cache
    • 데이터는 캐시해도 되지만, 항상 origin 서버에 검증하고 사용
  • cache-control: no-store
    • 데이터에 민감한 정보가 있으므로 저장하면 안 됨(메모리에서 사용하고 최대한 빨리 삭제)
  • cache-control: must-revalidate
    • 캐시 만료 후 최초 조회 시 origin 서버에 검증해야 함
    • origin 서버 접근 실패 시 반드시 오류가 발생해야 함 - 504(Gateway Timeout)
    • must-revlidate는 캐시 유효 시간이라면 캐시를 사용함
  • pragma: no-cache
    • HTTP 1.0 하위 호환을 위해서

댓글남기기