HTTP 는 텍스트 기반 프로토콜입니다. 요청과 응답 모두 같은 네 부분으로 이루어집니다.
| 구성 | 요청 예시 | 응답 예시 |
|---|---|---|
| 시작줄 | POST /members HTTP/1.1 |
HTTP/1.1 201 Created |
| 헤더 | Content-Type: application/json |
Content-Type: application/json |
| 빈 줄 | (헤더와 본문을 구분) | (헤더와 본문을 구분) |
| 본문 | {"name":"김철수"} |
{"id":1,"name":"김철수"} |
본문이 없는 요청(GET)이나 응답(204)은 빈 줄 뒤에 아무것도 없습니다. HttpClient 는 이 구조를 감춰 주지만, 실제로는 이 네 줄짜리 텍스트가 소켓으로 오갑니다.
https://api.example.com:8443/members?page=1&size=10#top 를 쪼개면 다음과 같습니다.
| 구성 | 값 | 의미 |
|---|---|---|
| scheme | https |
프로토콜(http, https) |
| host | api.example.com |
서버 주소 |
| port | 8443 |
포트, 생략하면 80·443 |
| path | /members |
자원 경로 |
| query | page=1&size=10 |
key=value, & 로 연결 |
| fragment | top |
서버에 전달되지 않는 클라이언트 표식 |
쿼리 값에 공백·한글·특수문자가 들어가면 URLEncoder.encode(값, StandardCharsets.UTF_8) 로 먼저 인코딩합니다. 인코딩 없이 붙이면 공백은 URL 을 끊고 한글은 서버가 다르게 해석합니다.
| 메서드 | 뜻 | 멱등 | 본문 |
|---|---|---|---|
| GET | 조회 | 예 | 없음 |
| POST | 생성 | 아니오 | 있음 |
| PUT | 전체 교체 | 예 | 있음 |
| DELETE | 삭제 | 예 | 보통 없음 |
멱등(idempotent)은 "같은 요청을 여러 번 보내도 결과가 같다" 는 뜻입니다. GET·PUT·DELETE 는 몇 번을 보내도 서버 상태가 같은 자리에 머물지만, POST 는 보낼 때마다 새 자원을 만들 수 있습니다. 그래서 네트워크 오류로 재시도할 때 POST 는 중복 생성을 조심해야 합니다.
첫 자리 숫자가 계열입니다. 1xx 정보성, 2xx 성공, 3xx 리다이렉션, 4xx 클라이언트 오류, 5xx 서버 오류입니다. 자주 보는 코드는 다음과 같습니다.
| 코드 | 계열 | 이름 | 자주 쓰는 상황 |
|---|---|---|---|
| 200 | 2xx | OK | 조회·수정 성공 |
| 201 | 2xx | Created | 생성 성공, Location 헤더 동반 |
| 204 | 2xx | No Content | 삭제 성공, 본문 없음 |
| 301 | 3xx | Moved Permanently | 영구 이동, URL 을 새로 외운다 |
| 302 | 3xx | Found | 임시 이동, 로그인 리다이렉트 |
| 304 | 3xx | Not Modified | 캐시 그대로 써도 됨 |
| 400 | 4xx | Bad Request | 요청 형식 오류 |
| 401 | 4xx | Unauthorized | 인증 안 됨(토큰 없음·만료) |
| 403 | 4xx | Forbidden | 인증은 됐지만 권한 없음 |
| 404 | 4xx | Not Found | 자원 없음 |
| 409 | 4xx | Conflict | 중복·버전 충돌 |
| 415 | 4xx | Unsupported Media Type | Content-Type 이 틀림 |
| 429 | 4xx | Too Many Requests | 호출 한도 초과 |
| 500 | 5xx | Internal Server Error | 서버 내부 오류 |
| 502 | 5xx | Bad Gateway | 앞단이 뒷단 응답을 못 받음 |
| 503 | 5xx | Service Unavailable | 서버 일시 중단·과부하 |
| 504 | 5xx | Gateway Timeout | 앞단이 뒷단 응답을 기다리다 포기 |
401 과 403 은 자주 헷갈립니다. 401 은 "누구인지 모른다", 403 은 "누군지는 알지만 자격이 없다" 입니다.
| 헤더 | 방향 | 뜻 |
|---|---|---|
Content-Type |
요청·응답 | 본문 형식, 예 application/json |
Accept |
요청 | 원하는 응답 형식 |
Authorization |
요청 | 인증 정보, 예 Bearer 토큰 |
User-Agent |
요청 | 클라이언트 식별 문자열 |
Cache-Control |
응답 | 캐시 정책 |
Location |
응답 | 201 응답의 새 자원 경로 |
Content-Length |
요청·응답 | 본문 바이트 수 |
Content-Type 을 빼고 JSON 을 보내면 서버가 본문을 못 읽어 400 이나 415 를 돌려줍니다. 요청·응답 양쪽에 붙는 헤더라는 점이 자주 헷갈립니다.
| JSON | 자바 |
|---|---|
객체 {"a":1} |
Map<String, Object> |
배열 [1,2] |
List<Object> |
문자열 "a" |
String |
숫자 1, 1.5 |
Long/Integer, Double |
불 true/false |
Boolean |
null |
null |
문자열 안의 ", \, 줄바꿈은 이스케이프해야 합니다(\", \\, \n). 유니코드는 가 형식으로도 쓸 수 있지만, UTF-8 로 그냥 써도 됩니다. JSON 은 표준적으로 UTF-8 입니다.
자바 객체를 JSON 문자열로 바꾸는 것이 직렬화, JSON 문자열을 자바 객체(또는 Map)로 바꾸는 것이 역직렬화입니다. 실무는 Jackson·Gson 이 @RestController 의 리턴 타입을 보고 자동으로 해 줍니다.
이 레슨은 그 자동화를 걷어내고 MiniJson 이라는 최소 구현으로 직접 문자열을 조립·파싱합니다. 라이브러리가 "그냥 되는" 일이 실제로는 따옴표 이스케이프, 재귀 파싱, 숫자·불·null 구분이라는 것을 한 번은 손으로 만져야 다음에 Jackson 예외가 났을 때 원인을 짚을 수 있습니다.
Java 11 부터 표준 라이브러리에 java.net.http.HttpClient 가 들어왔습니다. 외부 라이브러리 없이 GET·POST·PUT·DELETE 를 모두 보낼 수 있습니다.
| 타입 | 역할 |
|---|---|
HttpClient |
커넥션 풀을 쥔 클라이언트, 보통 하나 만들어 재사용 |
HttpRequest |
메서드·URL·헤더·본문·타임아웃을 담은 요청 |
HttpResponse<T> |
상태 코드·헤더·본문(T 타입)을 담은 응답 |
본문을 어떤 타입으로 받을지는 BodyHandlers 가 정합니다. ofString() 은 문자열로, ofFile(경로) 는 파일로 바로 저장합니다. 요청에는 connectTimeout(연결 자체의 제한 시간)과 timeout(응답을 기다리는 제한 시간) 을 따로 걸 수 있습니다.
client.send(...) 는 응답이 올 때까지 그 스레드를 막는 동기 호출이고, client.sendAsync(...) 는 CompletableFuture<HttpResponse<T>> 를 즉시 돌려주는 비동기 호출입니다. 여러 요청을 동시에 보낼 때는 sendAsync 를 씁니다.
같은 요청을 터미널에서 확인하려면 curl -X POST -H "Content-Type: application/json" -d '{"name":"김철수"}' 주소 처럼 씁니다. curl 의 -w, --connect-timeout, 종료 코드는 리눅스 운영 07 네트워크 진단 레슨에서 다룹니다.