Java에서 다른 서버로 API 요청 보내는 방법 총정리
알고 쓰자 요청 코드
여는글
안녕하세요. 이번 포스팅에서는 Java에서 특정 URI로 요청을 보내는 방법들에 대해 설명해드리려 합니다.
A서버에서 B서버로 요청을 보내는 상황을 떠올리면 마이크로서비스 아키텍처(Microservices Architecture)가 가장 먼저 떠올리게 되는데요. 하지만, 이런 아키텍처와 관련 없이도 외부 API와 시스템을 통합해야하는 상황이 빈번합니다. 따라서, 외부 API와 연동을 해서 데이터를 가져오거나 아니면 요청을 보내 처리해야 하는 경우엔 어떤 방법들을 사용할 수 있는지 알려드리도록 하겠습니다.
Java에서 다른 서버로 API 요청을 보내는 방법들
Java에서 다른 서버로 API 요청을 보내는 방법에는 다양한 방식이 존재합니다. 그 중에 대표적인 HTTP 요청 처리 방법에 대해 다뤄보겠습니다.
HttpURLConnection
HttpURLConnection
은 자바 표준 라이브러리에서 제공하는 가장 기본적인 HTTP 통신 방법입니다. 외부 라이브러리 없이 간단하게 요청을 보낼 수 있다는 장점이 있는데요. 특히 GET
, POST
, PUT
, DELETE
등 기본적인 HTTP 메서드를 지원하며, 간단한 네트워크 요청을 처리하는 데 자주 사용됩니다.
try {
// 1. URL 객체 생성
URL url = new URL("http://example.com");
// 2. 연결 생성 및 요청 방식 설정
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
// 3. 응답 코드 확인
int responseCode = connection.getResponseCode();
// 4. 응답 코드가 200(성공)일 때만 데이터 처리
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println("Response: " + content.toString());
} else {
System.out.println("GET request failed. Response Code: " + responseCode);
}
// 5. 연결 종료
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
간단한 요청의 경우 적합하지만, 앞으로 소개될 다른 방법들에 비해 코드의 가독성이 떨어지기도 하고, 특히, 설정 단계에서 헤더나 파라미터를 추가하는 과정이 다른 라이브러리보다 번거롭다는 단점이 있습니다. 특히, 동기적 방식으로만 요청이 처리되기 때문에, 요청이 완료될 때까지 스레드(thread)가 블록(block)됩니다. 이로 인해 대규모 또는 병렬 요청을 처리할 때 성능상의 문제가 발생할 수 있습니다. 비동기 처리나 이벤트 기반 방식이 필요한 경우 적합하지 않으며, 직접 스레드를 관리해야 하는 불편함이 있습니다.
Apache HttpClient
Apache HttpClient
는 위 HttpUrlConnection
의 한계를 보완하기 위해 사용되는 라이브러리 중 하나입니다. 간결한 API를 제공하는 것은 물론, 복잡한 HTTP 요청이나 인증 처리, 쿠키 관리 등을 쉽게 구현할 수 있습니다.
// 동기 처리
CloseableHttpClient client = HttpClients.createDefault();
HttpGet request = new HttpGet("http://example.com");
// 요청 실행 및 응답 받기
CloseableHttpResponse response = client.execute(request);
// 응답 처리
try {
System.out.println(EntityUtils.toString(response.getEntity()));
} finally {
response.close();
}
// 비동기 처리
HttpAsyncClient asyncClient = HttpAsyncClients.createDefault();
asyncClient.start();
HttpGet request = new HttpGet("http://example.com");
Future<HttpResponse> future = asyncClient.execute(request, null);
HttpResponse response = future.get();
System.out.println(EntityUtils.toString(response.getEntity()));
이 Apache HttpClient
는 HttpUrlConnection
과 다르게 비동기 요청도 처리 가능하고 HTTP/2
와 같은 최신 프로토콜을 지원을 합니다. 하지만, 라이브러리 의존성이 있다는게 단점이고 HttpUrlConnection
보다 비교적 무겁기 때문에, 단순한 요청에도 오버헤드가 발생할 수 있습니다.
OkHttp
OkHttp
는 가볍고 효율적인 HTTP 클라이언트로서, 동기 및 비동기 요청을 모두 지원하는 라이브러리입니다. 특히 Google이나 Square에서 사용한다고 알려져 있는데요. OkHttp
는 요청 처리 속도가 빠르고, Connection Pooling
을 통한 네트워크 효율성을 높일 수 있다는 장점을 가지고 있습니다.
// 동기 처리
// OkHttp 클라이언트 생성
OkHttpClient client = new OkHttpClient();
// 요청 객체 생성
Request request = new Request.Builder()
.url("http://example.com")
.build();
// 동기 요청 실행
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
System.out.println(response.body().string());
} else {
System.out.println("Request failed with code: " + response.code());
}
}
// 비동기 처리
// 비동기 요청 실행
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
System.out.println(response.body().string());
} else {
System.out.println("Request failed with code: " + response.code());
}
}
});
OkHttp는 HTTP/2
를 기본적으로 지원합니다.HTTP/2
는 요청 및 응답을 동시에 처리할 수 있는 멀티플렉싱 기능을 제공하여 대규모 트래픽 처리에서 성능을 최적화합니다. 특히 한 번의 연결을 통해 다수의 요청을 처리할 수 있어, 전송 속도가 매우 빠르고 효율적입니다. 우려되는 단점은 크게 없으나 앞서 설명한 Apache HttpClient
와 같이 오히려 단순한 요청에는 적합하지 않을 수 있습니다.
Spring RestTemplate
Spring RestTemplate
는 Spring Framework에서 제공하는 템플릿 라이브러리입니다. JSON이나 XML 포맷의 응답 처리를 지원하기 때문에, RESTful 서비스와의 상호작용을 쉽게 만들어주는데요. 주로 동기 방식의 요청을 처리합니다.
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://example.com", String.class);
기본적으로 동기식으로 동작하기 때문에, 대규모 요청 처리 시 성능 저하가 발생할 수 있습니다. 비동기 HTTP 요청 처리가 필요하다면 다른 대안을 찾긴 해야 합니다. 그보다 제일 중요한건 Spring
에서 Spring RestTemplate
을 Deprecated할 계획을 가지고 있습니다. 대신 다음으로 설명할 WebClient를 사용하도록 권장하고 있으며, 비동기 및 반응형 HTTP 클라이언트로 점차 대체되고 있는 추세입니다.
Spring WebClient
WebClient
는 Spring5 이후 도입된 HTTP 클라이언트입니다. RestTemplate
의 비동기 버전이라고 보시면 되는데요. Reactor기반으로 동작하며, 비동기식 및 반응형 프로그래밍 패러다임을 지원합니다. WebClient
는 기본적으로 Mono
와 Flux
를 사용해서 비동기적으로 데이터를 처리합니다. 간단히 설명하자면, Mono
는 단일값을 처리할 때고, Flux
는 여러 값을 처리할 때 사용합니다.
WebClient webClient = WebClient.create("http://example.com");
Mono<String> result = webClient.get()
.retrieve()
.bodyToMono(String.class);
result.subscribe(response -> System.out.println(response));
비동기 및 반응형 방식으로 동작하기 때문에 성능이 워낙 뛰어나며, 대규모 트래픽 처리에 적합합니다. 더 유연하고 다양한 기능을 제공하는 것도 물론인데요. 단점을 찾자면, 복잡한 설정 그리고 학습곡선이 높다는게 단점이 될 것 같습니다. 그리고 비동기식 요청의 특성상 자원 관리(스레드, 메모리 등)에 대한 추가적인 고려가 필요할 수 있습니다. 이를 적절히 관리하지 않으면 성능 저하나 메모리 누수와 같은 문제가 발생할 수 있습니다.
Feign
Feign
라이브러리는 Spring Cloud에서 제공하는 선언적 HTTP 클라이언트 라이브러리입니다. 따라서, Spring Cloud로 만들어진 마이크로서비스 아키텍처(Microservices Architecture)에서 사용할 수 있습니다.
@FeignClient(name = "exampleClient", url = "http://example.com")
public interface ExampleClient {
@GetMapping("/data")
String getData();
}
위처럼 선언적으로 사용하기 때문에, 비즈니스 로직에서 HTTP 통신 코드를 분리할 수 있다는 장점이 있습니다. 복잡한 커스터마이징은 어렵지만, 서비스 간 통신이 잦은 마이크로서비스 환경에서 유용하다는 장점이 있습니다.
API 요청 예외 처리 방법
API요청을 보내는 각각의 방법들 모두가 상황에 맞는 장단점을 가지고 있습니다. 하지만, 우리가 특히 신경 써줘야 할 것은 역시나 예외처리인데요.
시스템 내부 논리적 오류로 발생하는 것들은 예측이 가능하거나 적절한 예외처리를 통해 대응할 수 있습니다. 그러나 API 요청은 네트워크 오류, 서버 오류 등 다양한 이유로 실패할 수 있다는 문제가 있습니다. 따라서 이번 목차에서는 API 요청 중 발생할 수 있는 주요 예외 상황과 이를 처리하는 방법을 살펴보겠습니다.
시간 초과
API 요청은 특정 시간이 경과한 후에도 응답을 받지 못할 경우 시간 초과 예외를 발생시킵니다. 서버가 응답을 하지 않아서일수도 있고, 네트워크 연결이 불안정해서 일수도 있습니다. 따라서, 이에 대한 경우을 예상하고 방지해야하는데요.
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 연결에 걸리는 최대 시간
.readTimeout(30, TimeUnit.SECONDS) // 서버 응답을 기다리는 최대 시간
.writeTimeout(30, TimeUnit.SECONDS) // 서버로 데이터를 쓰는 시간 제한
.build();
이런식으로 타임아웃(Timeout)을 직접 설정함으로서 서버의 안정성을 높일 수 있습니다.
재시도 로직
API 요청이 실패하는 경우에도 재시도 로직을 설정하여, 일정 시간 간격을 두고 여러번 요청을 재시도하는 방식을 사용할수도 있습니다.
하지만, 이 방법은 개인적으로 권장하지는 않습니다. 말로만 풀어냈을때는 단순히 요청만 재시도하는것으로 보여 예외처리를 간단하게 해결할 수 있을 것처럼 보이지만, 요청이 중복처리 될 가능성이나 요청을 처리하는 다른 서버 측 오버헤드도 고려되어야 합니다. 그리고 서버의 리소스가 요청을 재시도 하는데만 집중되어버리는 상황 등 단순하게만 접근할 것이 아닙니다. 따라서, 재시도 횟수, 요청 간격 그리고 요청 실패에대한 사용자 응답을 작성해주는것이 좋습니다.
API 보안처리
API 요청시 보안 또한, 보장되어야 합니다. 특히 금융 시스템이나 개인 정보를 다뤄야하는 API라면 데이터 전송중 보안 문제는 각별히 신경을 써줘야 합니다.
HTTPS
제일 기본적인 보안 버전입니다. HTTP요청에 SSL/TLS 프로토콜을 통해 데이터를 암호화햐여 전송하는 프로토콜인데요. 이를 통해 중간 공격자가 데이터를 탈취하거나 변조하는 것을 방지할 수 있습니다.
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.build();
API 요청 성능 최적화
대규모 트래픽을 처리해야 하는 시스템이라면 API 요청 성능에대한 성능 최적화가 필수입니다. 따라서, API 요청 성능을 최적화하는 방법들도 설명해드리도록 하겠습니다.
Connection Pooling
Connection Pooling
은 여러 HTTP 요청 시 매번 새로운 연결을 생성하는 대신, 이미 생성된 연결을 재사용해서 성능을 향상시키는 기법입니다. 이를 통해 연결 생성 및 해제에 드는 오버헤드를 줄일 수 있습니다.
요청 병렬 처리
여러 API 요청을 병렬로 처리함으로써 응답 시간을 단축할 수 있습니다. Java에서는 ExecutorService
나 CompletableFuture
를 사용해서 비동기식으로 여러 요청을 동시에 처리할 수 있습니다.
정리
방법 | 동기/비동기 | 주요 강점 | 주요 단점 | 적합한 사용 사례 |
---|---|---|---|---|
HttpURLConnection | 동기 | 외부 라이브러리 불필요, 기본 HTTP 지원 | 고급 기능 구현이 복잡, 비동기 지원 부족 | 소규모 프로젝트에서 간단한 HTTP 요청 처리 |
Apache HttpClient | 동기 및 비동기 | 포괄적인 기능 제공, 고급 인증 및 연결 관리 지원 | 상대적으로 무거움, 외부 라이브러리 의존 | 복잡한 HTTP 통신과 인증, 보안이 필요한 대규모 프로젝트 |
OkHttp | 동기 및 비동기 | 효율적, 연결 풀링 지원, 경량 | 외부 라이브러리 의존, 고급 사용 시 복잡성 증가 | 효율적이고 확장 가능한 HTTP 요청이 필요한 프로젝트 |
RestTemplate | 동기 | Spring 통합, 간단한 API, RESTful 서비스 지원 | 동기 방식, Spring 5에서 Deprecated 예정 | 간단한 REST API 호출이 필요한 Spring 애플리케이션 |
WebClient | 비동기 | 반응형, 비동기 프로그래밍 지원, 고부하 환경에 적합 | 학습 곡선이 가파름, 복잡한 설정 필요, 자원 관리 필요 | 대규모 트래픽 및 고성능이 요구되는 애플리케이션 |
결론
실무에서는 생각보다 외부 API와 통합해야하는 상황이 많습니다. 각 라이브러리 장단점을 숙지하고, 상황에 맞게 사용해서 코드의 가독성과 시스템 안정성을 확보하면 좋을 것 같습니다.