현록

HTTP Content-Type과 Accept 차이

API 요청에 Content-Type: application/jsonAccept: application/json을 함께 넣는 코드를 자주 본다.
값은 같아 보여도 두 헤더가 설명하는 대상은 다르다.

Content-Type은 현재 메시지에 담긴 콘텐츠의 형식을 설명한다.
요청의 Accept는 클라이언트가 응답으로 받을 수 있는 형식을 서버에 알린다.

두 헤더의 방향

HTTP 요청과 응답에는 각각 헤더와 콘텐츠가 있을 수 있다.
Content-Type은 자신이 포함된 요청이나 응답의 실제 콘텐츠를 가리킨다.

일반적인 API 호출에서 Accept는 요청에 포함되며 앞으로 받을 응답의 형식에 대한 선호를 가리킨다.
요청 본문의 형식을 설명하지 않는다.

RFC 9110은 서버가 응답에 Accept를 넣어 같은 리소스로 이어질 요청 콘텐츠에서 선호하는 미디어 타입을 알리는 용도도 정의한다.
이 글에서는 웹 API에서 가장 흔한 요청 헤더 용법을 기준으로 비교한다.

요청 메시지는 다음과 같다.

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{"name":"Dohyun"}

응답 메시지는 다음과 같다.

HTTP/1.1 201 Created
Content-Type: application/json

{"id":42,"name":"Dohyun"}

요청은 JSON 본문을 보내고 JSON 응답을 원한다는 뜻이다.
응답은 실제로 JSON 콘텐츠를 보냈다고 설명한다.
요청 본문을 보내지 않는 일반적인 GET에서는 요청 Content-Type이 필요하지 않은 경우가 많지만 Accept는 여전히 응답 선호를 전달할 수 있다.
method와 body의 기본 기준은 HTTP GET과 POST 차이에서 더 자세히 볼 수 있다.

Content-Type의 역할

요청의 Content-Type은 서버가 본문을 어떤 방식으로 해석해야 하는지 알려준다.
JSON 문자열에는 application/json, 일반 HTML 폼 전송에는 application/x-www-form-urlencoded, 파일을 포함한 폼에는 multipart/form-data가 주로 사용된다.

응답의 Content-Type은 클라이언트가 받은 표현의 실제 형식을 설명한다.
요청과 응답의 Content-Type은 서로 독립적이다.
서버가 JSON 요청을 받은 뒤 HTML이나 빈 응답을 보낼 수도 있으므로 각 메시지의 실제 콘텐츠에 맞춰야 한다.

Accept의 역할

Accept는 서버가 같은 리소스를 여러 형식으로 제공할 수 있을 때 콘텐츠 협상의 기준이 된다.
클라이언트는 쉼표로 여러 media type을 보내고 q 값으로 상대적인 선호도를 나타낼 수 있다.

Accept: text/html, application/json;q=0.8, */*;q=0.1

이 값은 HTML을 가장 선호하고, JSON은 그보다 낮은 우선순위로 허용하며, 다른 형식도 낮은 우선순위로 받을 수 있다는 뜻이다.
*/*는 모든 media type을 가리킨다.

Accept가 없으면 클라이언트가 응답의 media type에 별도 선호를 두지 않은 것으로 해석할 수 있다.
서버가 Accept에 따라 응답을 바꾼다면 캐시가 형식별 응답을 구분하도록 Vary: Accept도 함께 고려해야 한다.

JSON 요청과 응답

Fetch API로 JSON을 보낼 때는 객체를 문자열로 변환하고 요청 본문의 형식을 명시한다.

const response = await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({ name: 'Dohyun' }),
})

const user = await response.json()

Content-Type을 적는 것만으로 JavaScript 객체가 JSON 문자열로 바뀌지는 않는다.
JSON.stringify()가 요청 본문을 직렬화하고, 헤더는 그 결과의 형식을 설명한다.

반대로 response.json()은 응답 본문을 읽고 JSON으로 파싱하는 동작이다.
HTTP 요청 도구의 처리 차이는 fetch와 axios 차이에서 이어서 볼 수 있다.

상태 코드와 구현 주의점

서버가 요청의 Content-Type을 지원하지 않으면 415 Unsupported Media Type으로 응답할 수 있다.
JSON만 받는 API에 text/plain 본문을 보낸 경우가 여기에 가깝다.

서버가 Accept 조건에 맞는 응답을 만들 수 없고 해당 선호를 무시한 응답도 보내지 않기로 했다면 406 Not Acceptable을 사용할 수 있다.
두 상태를 구분하면 요청 본문의 문제인지 응답 협상의 문제인지 빠르게 찾을 수 있다.

FormData를 보낼 때는 Content-Type: multipart/form-data를 직접 지정하지 않아야 한다.

const formData = new FormData()
formData.append('avatar', file)

await fetch('/api/profile', {
  method: 'POST',
  body: formData,
})

브라우저가 각 필드를 나누는 boundary를 포함한 Content-Type을 자동으로 만든다.
개발자가 헤더를 직접 덮어쓰면 boundary가 빠져 서버가 본문을 해석하지 못할 수 있다.

교차 출처 요청에서 application/json 같은 Content-Type은 preflight 조건에도 영향을 줄 수 있다.
이 경우 헤더를 지우는 방식보다 서버의 허용 설정을 CORS 에러와 해결 기준에 맞춰 확인해야 한다.

참고 자료

관련 포스트
Git fetch와 pull 차이 thumbnail
Git fetch와 pull 차이
Git fetch와 pull이 원격 변경을 가져오고 현재 브랜치에 통합하는 방식을 정리합니다. remote-tracking branch, upstream, fast-forward, merge와 rebase 선택 기준을 함께 봅니다.
HTTP PUT과 PATCH 차이 thumbnail
HTTP PUT과 PATCH 차이
HTTP PUT과 PATCH의 차이를 리소스 교체와 변경 명령, 멱등성, JSON Patch와 JSON Merge Patch, ETag를 이용한 동시 수정 방지 기준으로 정리합니다.
HTML label과 input 연결 방법 thumbnail
HTML label과 input 연결 방법
HTML label과 input을 for와 id로 연결하는 방법, 암시적 연결, 폼 그룹과 보조 설명, 접근성 오류 패턴을 정리합니다.
HTML section과 article 차이 thumbnail
HTML section과 article 차이
HTML section과 article의 의미, 독립성 판단 기준, 중첩 구조, 제목과 접근성을 고려한 선택 방법을 정리합니다.
HTML div와 span 차이 thumbnail
HTML div와 span 차이
HTML div와 span의 차이를 기본 display, 담을 수 있는 내용, 시맨틱 태그, CSS와 JavaScript에서 그룹화하는 기준으로 정리합니다.
HTTP 상태 코드 정리 thumbnail
HTTP 상태 코드 정리
HTTP 상태 코드의 2xx, 3xx, 4xx, 5xx 의미와 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502, 503 차이를 정리합니다.
HTML id와 class 차이 thumbnail
HTML id와 class 차이
HTML id와 class의 차이를 문서 내 유일성, 여러 값 사용, CSS 선택자, JavaScript 탐색, fragment 링크와 이름 작성 기준으로 정리합니다.
HTML button과 a 태그 차이 thumbnail
HTML button과 a 태그 차이
HTML button과 a 태그의 차이를 이동과 동작의 의미, href, type 속성, 폼 제출, 키보드 접근성, 잘못된 사용 패턴으로 정리합니다.
HTTP GET과 POST 차이 thumbnail
HTTP GET과 POST 차이
HTTP GET과 POST의 차이를 초보자 기준으로 정리합니다. 조회와 변경, query string과 request body, 캐시, safe, idempotent, form과 fetch 사용 기준을 함께 봅니다.
npm outdated와 npm update 차이 thumbnail
npm outdated와 npm update 차이
npm outdated와 npm update의 차이를 초보자 기준으로 정리합니다. Current, Wanted, Latest의 의미와 semver 범위 안에서 업데이트되는 방식, package-lock.json 변화까지 함께 봅니다.
npm install -g와 npx 차이 thumbnail
npm install -g와 npx 차이
npm install -g와 npx의 차이를 초보자 기준으로 정리합니다. 전역 설치, 로컬 설치, 일회성 실행, 프로젝트 scripts에 넣는 기준까지 함께 봅니다.
package.json에서 ^와 ~ 차이 thumbnail
package.json에서 ^와 ~ 차이
package.json 의존성 버전 앞에 붙는 ^와 ~의 차이를 초보자 기준으로 정리합니다. semantic versioning, 버전 범위, 0.x 예외, package-lock.json과의 관계까지 함께 봅니다.
Git merge와 rebase 차이 thumbnail
Git merge와 rebase 차이
Git merge와 rebase가 커밋 그래프를 어떻게 바꾸는지 정리합니다. fast-forward, merge commit, rebase의 커밋 ID 변경, 충돌 처리, 공유 브랜치에서의 안전한 사용 기준까지 살펴봅니다.
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
npm scripts와 npm run 정리 thumbnail
npm scripts와 npm run 정리
npm scripts란 무엇인지, package.json scripts와 npm run의 관계를 초보자 기준으로 정리합니다. npm run dev, npm start, npm test, node_modules/.bin, -- 인자 전달 방식까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci와 npm install 차이 thumbnail
npm ci와 npm install 차이
npm ci와 npm install의 차이를 초보자 기준으로 정리합니다. clean install의 의미, package-lock.json 조건, CI에서 npm ci를 쓰는 이유, .npmrc 플래그 주의점까지 함께 봅니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI 차이 thumbnail
TTV와 TTI 차이
TTV와 TTI의 차이를 초보자 기준으로 정리합니다. 사용자가 화면을 보는 시점, 상호작용 가능한 시점, FCP, LCP, INP, Core Web Vitals와의 관계까지 함께 봅니다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.