Java 26 - HTTP/3 for the HTTP Client API
업데이트:
JAVA 261
Java 26에서는 HTTP Client2 API에 HTTP/33 프로토콜 지원이 추가되었습니다. HTTP/3는 UDP 기반의 QUIC 프로토콜4을 사용하여 더 빠른 핸드셰이크, 낮은 지연시간, 그리고 높은 패킷 손실 환경에서의 더 안정적인 전송을 제공합니다.
HTTP/3의 장점
- 빠른 연결 수립: 0-RTT(Round Trip Time) 핸드셰이크 지원
- 머리-줄-세우기 차단 제거: 독립적인 스트림 처리로 병렬 요청 효율 증가
- 네트워크 마이그레이션: WiFi에서 LTE로 전환 시 연결 유지
- 더 안정적인 전송: 패킷 손실이 많은 환경에서 우수한 성능
기본 사용법
1. HttpClient에서 HTTP/3 활성화
import java.net.http.*;
import java.net.URI;
public class BasicHttp3Example {
public static void main(String[] args) throws Exception {
// HTTP/3을 사용하도록 설정
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://httpbin.org/get")
)
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("상태 코드: " + response.statusCode());
System.out.println("버전: " + response.version());
System.out.println("응답 길이: " + response.body().length());
}
}
실행 결과:
상태 코드: 200
버전: HTTP_3
응답 길이: 425
요청별 버전 지정
2. 개별 요청에 HTTP/3 적용
import java.net.http.*;
import java.net.URI;
public class Http3PerRequestExample {
public static void main(String[] args) throws Exception {
// 클라이언트는 HTTP/2 기본값 유지
HttpClient client = HttpClient.newHttpClient();
// 특정 요청에서만 HTTP/3 사용
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://api.example.com/data")
)
.version(HttpClient.Version.HTTP_3)
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("응답 버전: " + response.version());
System.out.println("상태: " + response.statusCode());
}
}
HTTP/3 프로토콜 협상
3. 자동 프토토콜 다운그레이드
import java.net.http.*;
import java.net.URI;
public class Http3AutoDowngradeExample {
public static void sendWithFallback(String url) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
HttpRequest request = HttpRequest.newBuilder(
URI.create(url)
)
.GET()
.build();
try {
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("URL: " + url);
System.out.println("사용된 프로토콜: " + response.version());
System.out.println("상태: " + response.statusCode());
} catch (Exception e) {
System.err.println("요청 실패: " + e.getMessage());
}
}
public static void main(String[] args) throws Exception {
// HTTP/3을 지원하는 서버
sendWithFallback("https://www.google.com");
// HTTP/3을 지원하지 않으면 자동으로 HTTP/2로 다운그레이드
sendWithFallback("https://api.example.com/old-server");
}
}
병렬 요청
4. 여러 요청을 HTTP/3으로 동시 실행
import java.net.http.*;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Http3ParallelRequestsExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
List<String> urls = Arrays.asList(
"https://httpbin.org/uuid",
"https://httpbin.org/user-agent",
"https://httpbin.org/headers"
);
// 비동기 요청 생성
List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
.map(url -> {
HttpRequest request = HttpRequest.newBuilder(
URI.create(url)
)
.GET()
.build();
return client.sendAsync(
request,
HttpResponse.BodyHandlers.ofString()
);
})
.collect(Collectors.toList());
// 모든 요청이 완료될 때까지 대기
CompletableFuture<Void> allFutures =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
allFutures.thenRun(() -> {
System.out.println("모든 요청 완료:");
futures.forEach(f -> {
try {
HttpResponse<String> response = f.join();
System.out.println("상태: " + response.statusCode() +
", 프로토콜: " + response.version());
} catch (Exception e) {
e.printStackTrace();
}
});
}).join();
}
}
실행 결과:
모든 요청 완료:
상태: 200, 프로토콜: HTTP_3
상태: 200, 프로토콜: HTTP_3
상태: 200, 프로토콜: HTTP_3
Alternative Services 발견
5. HTTP Alternative Services (Alt-Svc) 사용
import java.net.http.*;
import java.net.URI;
public class Http3AltSvcExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 첫 요청은 HTTP/2
.build();
// 첫 번째 요청: HTTP/2로 시작하지만 Alt-Svc 헤더 수신
HttpRequest request1 = HttpRequest.newBuilder(
URI.create("https://api.example.com/resource")
)
.GET()
.build();
HttpResponse<String> response1 = client.send(
request1,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("첫 번째 요청:");
System.out.println("프로토콜: " + response1.version());
System.out.println("Alt-Svc: " + response1.headers()
.firstValue("alt-svc")
.orElse("없음"));
// 두 번째 요청: 같은 서버에 대해 HTTP/3 지원이 감지되었으면 사용
HttpRequest request2 = HttpRequest.newBuilder(
URI.create("https://api.example.com/data")
)
.version(HttpClient.Version.HTTP_3)
.GET()
.build();
HttpResponse<String> response2 = client.send(
request2,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("\n두 번째 요청:");
System.out.println("프로토콜: " + response2.version());
}
}
POST 요청과 본문 전송
6. HTTP/3로 POST 요청
import java.net.http.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
public class Http3PostExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
String jsonPayload = """
{
"name": "John",
"email": "john@example.com",
"age": 30
}
""";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://api.example.com/users")
)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("상태 코드: " + response.statusCode());
System.out.println("프로토콜: " + response.version());
System.out.println("응답: " + response.body());
}
}
에러 처리와 타임아웃
7. HTTP/3 요청의 에러 처리
import java.net.http.*;
import java.net.URI;
import java.time.Duration;
public class Http3ErrorHandlingExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://api.example.com/timeout")
)
.timeout(Duration.ofSeconds(5))
.GET()
.build();
try {
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
// 상태 코드 확인
if (response.statusCode() >= 400) {
System.err.println("오류: 상태 코드 " + response.statusCode());
System.err.println("응답: " + response.body());
} else {
System.out.println("성공: " + response.statusCode());
}
} catch (java.net.http.HttpTimeoutException e) {
System.err.println("타임아웃: " + e.getMessage());
} catch (java.io.IOException e) {
System.err.println("네트워크 오류: " + e.getMessage());
} catch (InterruptedException e) {
System.err.println("중단됨: " + e.getMessage());
Thread.currentThread().interrupt();
}
}
}
응답 헤더 및 메타데이터
8. HTTP/3 응답 정보 확인
import java.net.http.*;
import java.net.URI;
import java.util.List;
import java.util.Map;
public class Http3ResponseMetadataExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://httpbin.org/response-headers")
)
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("=== 응답 정보 ===");
System.out.println("상태 코드: " + response.statusCode());
System.out.println("버전: " + response.version());
System.out.println("URI: " + response.request().uri());
System.out.println("\n=== 헤더 ===");
response.headers().map().forEach((name, values) -> {
System.out.println(name + ": " + String.join(", ", values));
});
System.out.println("\n=== 본문 크기 ===");
System.out.println("크기: " + response.body().length() + " bytes");
}
}
실행 결과:
=== 응답 정보 ===
상태 코드: 200
버전: HTTP_3
URI: https://httpbin.org/response-headers
=== 헤더 ===
content-type: application/json
content-length: 123
server: gunicorn/19.9.0
=== 본문 크기 ===
크기: 123 bytes
HTTP/3 마이그레이션 가이드
기존 HTTP/2 코드
// 기존 HTTP/2 코드
HttpClient client = HttpClient.newHttpClient();
HTTP/3으로 마이그레이션
// HTTP/3 사용
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
// 또는 자동 다운그레이드와 함께
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
주요 특징
| 기능 | 설명 |
|---|---|
| 자동 다운그레이드 | HTTP/3 미지원 시 자동으로 HTTP/2 또는 HTTP/1.1로 변경 |
| 기본값 유지 | 기본 버전은 여전히 HTTP/2 (선택적 HTTP/3) |
| API 호환성 | 기존 코드 수정 최소화 |
| 성능 | 더 빠른 핸드셰이크, 낮은 지연시간 |
주의사항
- 선택적 사용: HTTP/3은 기본이 아니므로 명시적으로 활성화 필요
- 서버 지원: 모든 서버가 HTTP/3을 지원하지는 않음
- 파이어월: 일부 방화벽이 QUIC(UDP 포트 443) 차단 가능
- 모니터링: HTTP/3 요청의 실패를 처리할 수 있도록 구현
성능 개선 예상 효과
- 연결 수립 시간: 50-90% 감소
- 페이지 로드 시간: 10-30% 감소
- 네트워크 복구: 패킷 손실 환경에서 우수
댓글남기기