1. 개요
외부 API를 호출할 일이 생겨, 비슷한 다른 로직을 살펴보던 중 HttpURLConnection을 사용하는 것을 보았다.
지난 졸업작품 프로젝트를 하면서 WebClient를 사용하여 네이버 지도 API를 호출한 적이 있었는데
이번 글을 기회로 "각 HTTP 요청 연결의 사용 시점"에 대해 정리하고자 글을 작성하게 됐다.
참고로 졸업작품에서는 N^2 (N <= 5) 건에 해당하는 API 호출,
인턴 업무에서는 5000~6000 건에 해당하는 API 호출이 필요하여 WebClient를 사용했다.
2. HTTP 통신 방식
공공 데이터 포털에서 제공하는 Open API나 기업에서 제공하는 API를 사용하기 위해서,
요구하는 Parameter / Body / Header 형식을 갖춘 HTTP 요청을 만들어야 한다.
서버 간의 HTTP 통신을 위해 기본적이고, 대표적으로 많이 사용하고 있는 방법들을 알아보자.
1) HttpURLConnection
java에서 제공하는 표준 라이브러리이다.
동작방식
: URL로부터 HttpURLConnection 객체를 생성하여
'타임아웃, 캐시, HTTP 메소드 타입, 헤더 등' 과 같이, 요청을 구성하고 응답을 조회하기 위한 다양한 메소드들을 제공한다.
장점
- 외부 의존성이 필요 X
- 필요한 기능만 제공하므로 비교적 가벼움
- 메소드들이 직관적이고 간단함
단점
- 동기 방식으로 동작함
- JSON 파싱이나 파일 업로드에서 코드가 복잡해짐
(응답에 대한 처리에서 버퍼를 이용하여 일일이 처리해주어야 함)
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class HttpExample {
public static void main(String[] args) {
String urlString = "http://example.com/api/resource"; // 요청할 URL
try {
// 1. URL 객체 생성
URL url = new URL(urlString);
// 2. URL로부터 HttpURLConnection 객체 생성
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 3. 요청 메소드 설정 (GET)
connection.setRequestMethod("GET");
// 4. 요청 헤더 설정 (필요한 경우)
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
// 5. 응답 코드 확인
int responseCode = connection.getResponseCode();
// 6. 응답이 성공적인 경우 (200 OK)
if (responseCode == HttpURLConnection.HTTP_OK) {
// 6-1. InputStream을 사용하여 응답 본문 읽기
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
// 6-2. 응답 문자열을 JSON 형식으로 변환 및 출력
String responseString = response.toString();
JsonElement jsonElement = JsonParser.parseString(responseString);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(jsonElement);
System.out.println("Response: " + jsonOutput);
}
// 7. 응답이 실패한 경우
else {
System.out.println("GET request not worked");
}
// 8. 연결 종료
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2) RestTemplate
Spring 3.0부터 제공되는 Blocking HTTP Client이다.
동작방식
내부적으로 HttpURLConnection을 사용하여 HTTP 요청을 처리한다.
Java Servlet API를 활용하고 있기 때문에 기본적으로 하나의 스레드당 하나의 요청 방식으로 되어있다.
이는 클라이언트가 응답을 받을 때까지 스레드는 Block 상태이기 때문에 서버 자원과 응답 시간에 영향을 미치는 것을 의미한다. 또한 많은 요청이 들어오면 이에 비례하여 스레드가 생성되어 메모리와 스레드풀에 영향을 미치고, 잦은 Context Switching으로 인해 성능이 저하된다.
장점
- 멀티쓰레드 방식
- Blocking 방식
- 동기 방식
- JSON, XML 등의 응답을 자동으로 자바 객체로 변환함
- 간편하게 RESTful API와 통신이 가능
단점
- 비동기 처리 지원 X
(비동기 클라이언트로 AsyncRestTemplate을 사용했지만, 현재 deprecated 된 상태)
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class RestTemplateExample {
public static void main(String[] args) {
String urlString = "http://example.com/api/resource"; // 요청할 URL
try {
// 1. RestTemplate 객체 생성
RestTemplate restTemplate = new RestTemplate();
// 2. 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("Header-Key", "Header-Value");
// 3. HttpEntity 생성 (헤더와 본문 포함)
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
// 4. POST 요청 보내기 및 응답 받기
ResponseEntity<String> responseEntity = restTemplate.exchange(urlString, HttpMethod.POST, entity, String.class);
// 5. 응답 코드 확인
int responseCode = responseEntity.getStatusCodeValue();
// 6. 응답이 성공적인 경우 (200 OK)
if (responseEntity.getStatusCode().is2xxSuccessful()) {
// 6-1. 응답 문자열을 JSON 형식으로 변환 및 출력
String responseString = responseEntity.getBody();
JsonElement jsonElement = JsonParser.parseString(responseString);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(jsonElement);
System.out.println("Response: " + jsonOutput);
}
// 7. 응답이 실패한 경우
else {
System.out.println("GET request not worked");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3) WebClient
Spring 5.0부터 제공되는 최신 Non-Blocking Reactive HTTP Client이다.
동작방식
Non-Blocking을 지원하기 위해 Spring Reactive Framework를 기반으로 개발됐다.
Reactive Framework는 Event-Driven 구조를 사용하기 때문에 비동기 로직을 Reactive Stream API를 통해 제공하게 되는데, 이는 RestTemplate와 비교하여 적은 양의 스레드와 시스템 자원만으로 많은 요청을 처리할 수 있음을 의미하게 된다.
이 때문에 많은 양의 요청을 병렬로 처리해야하는 경우 적합한 HTTP Client로 볼 수 있다.
장점
- 싱글 스레드 방식
- Non-Blocking 방식
시스템을 호출한 직후에 프로그램으로 제어가 다시 돌아와서 시스템 호출의 종료를 기다리지 않고 다음 동작을 진행한다. 호출한 시스템의 동작을 기다리지 않고 동시에 다른 작업을 진행할 수 있어서 작업의 속도가 빨라진다는 장점이 있다.
- JSON, XML 등의 응답을 자동으로 자바 객체로 변환
- 간편하게 RESTful API와 통신 가능
단점
- 외부 의존성이 필요
// webflux
implementation 'org.springframework.boot:spring-boot-starter-webflux'
3. 정리
마지막으로 가장 헷갈리는 동기/비동기와 Blocking/Non-Blocking에 대해서 정리하고,
본 글의 목표인 "각 HTTP 요청 연결의 사용 시점" 에 대해 정리하고 마무리하자!!
1) 제어권 반환
제어권
: 자신의 코드를 실행할 수 있는 권리
Blocking
: Application(A)이 kernel(B)로 작업 요청을 할 때 제어권을 준다. (그럼 A는 blocking이 됨)
제어권을 가진 B는 요청에 대한 로직을 실행한다. 하지만 제어권을 잃은 A는 B의 응답을 받을 때까지 대기를 한다.
또한 Application은 kernel이 작업을 끝낼 때까지 백그라운드에서 작업이 끝났는지 지속적으로 확인한다.
Non-Blocking
: Application이 kernel에 작업 요청을 할 때 제어권을 주지 않는다.
B는 요청에 대한 로직을 실행한다. 제어권을 계속 갖고 있는 A는 B를 호출한 이후에도 자신의 코드를 계속 실행한다.
따라서, B가 요청에 대한 처리를 하는 동안 A는 다른 로직을 실행할 수 있게 된다.
2) 결과값을 지속적으로 체크하는가?
결과값을 확인함
: 호출되는 함수의 작업 완료 여부를 확인하는지 유무
동기
: Application(A)이 kernel(B)에게 작업 요청을 한다.
그리고 A는 B의 작업이 완료되었는지 지속적으로 확인한다.
비동기
: Application(A)이 kernel(B)에게 작업 요청을 한다.
이 때 콜백 함수를 함께 전달하는데, B의 작업이 완료되면 콜백 함수가 실행되어 A에게 알린다.
그리고 A는 B의 작업이 완료되었는지 확인하지 않는다.
3) 실무 사례
대표적으로 Node.js(비동기) + MySQL(Blocking)
1. JavaScript가 비동기 방식으로 MySQL에 쿼리를 전송한다.
2. MySQL은 쿼리를 처리하면서 JavaScript에게 제어권을 넘겨주지 않는다. (Blocking)
3. JavaScript는 다른 작업을 수행할 수 없으며, MySQL의 쿼리 실행값이 필요하기 때문에 기다린다.
4. 비동기 콜백 함수가 MySQL의 실행 결과를 알려주고, 이후 JavaScript 작업이 실행된다.
결과적으로 동기 + Blocking 방식의 작업 수행과 차이가 없게 된다..
따라서 Node.js 서버 프로그래밍을 할 때에는 아예 async/await로 동기 처리를 한다고 한다.
동기 | 비동기 | |
Blocking 제어권 X |
A 함수 대기 B 함수 실행이 완료 시, 다시 재개 |
A 함수 대기 B 함수 실행이 완료되었는지 체크 X |
Non-Blocking 제어권 O |
A 함수 계속 실행 B 함수 실행이 완료되었는지 체크 O |
A 함수 계속 실행 B 함수 실행이 완료되었는지 체크 X |
HttpURLConnection | RestTemplate | WebClient | |
외부 의존성 | X (java) | △ (spring 3.0) | O (spring 5.0 + WebFlux) |
동기 / 비동기 | 동기 | 동기 | 비동기 |
Blocking / Non-Blocking |
Blocking | Blocking | Non-Blocking |
쓰레드 | 싱글 | 멀티 | 싱글 |
사용 시점 | 간단한 HTTP 요청 외부 라이브러리 사용 제한 작은 애플리케이션 or 단순한 작업 |
Spinrg 5.0 이전 시스템 | Spring 5.0 이후 시스템 대규모 병렬 HTTP 요청 처리 작업 비동기적이고 고성능 요구 작업 |
참고 문헌
1) https://gngsn.tistory.com/154
'Spring > 끄적끄적' 카테고리의 다른 글
Spring Boot에서의 엑셀 다운로드 API (0) | 2024.07.20 |
---|---|
@JoinColumn(name =" ", referecedColumnName= " ") (0) | 2024.07.15 |
WebClient 네이버 지도 비동기 API 호출하기 (2) (1) | 2024.05.20 |
WebClient 네이버 지도 비동기 API 호출하기 (1) (0) | 2024.05.20 |