๐[Spring] WebClient - 1. ๊ฐ๋ ๋ฐ ๊ธฐ๋ณธ ์ค์
์ด์ ์ฅ์์ ๊ธฐ์ ํ WebClient ๊ธฐ๋ณธ ์ค์ ์ด ์๋ฃ๋๊ณ ๋๋ฉด,
์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์์ฒญ์ ๋ฐ๊ณ ํธ๋ค๋งํ ์ ์๋ API ๊ตฌ์ฑ์ด ํ์ํ๋ค.
๐โ RequestHandler
๋จผ์ ์ค์ HTTP ์์ฒญ์ ์คํํ๊ณ ํธ๋ค๋งํ๋ ์ธํ๋ผ ๊ณ์ธต ์๋น์ค๋จ์ ์์ฑํ๋ค.
@Service
@RequiredArgsConstructor
public class RequestHandler {
private final ObjectMapper mapper;
/**
* WebClient GET ์์ฒญ ํ ๊ฒฐ๊ณผ ๋ฌธ์์ด ๋ฐํ
*
* @param webClient WebClient
* @param url String
* @param params Map<String, String>
* @return String
*/
public String sendGetRequest(WebClient webClient, String url, Map<String, String> params) { // GET request
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.setAll(params);
URI uri = UriComponentsBuilder
.fromUriString(url)
.queryParams(queryParams)
.build().toUri();
Mono<String> mono = webClient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Bad Request - Error Code " + response.statusCode().value())))
.onStatus(HttpStatusCode::is5xxServerError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Internal Server Error - Error Code " + response.statusCode().value())))
.bodyToMono(String.class);
return mono.block();
}
/**
* WebClient POST ์์ฒญ ํ ๊ฒฐ๊ณผ ๋ฌธ์์ด ๋ฐํ
*
* @param webClient WebClient
* @param url String
* @param params Map<String, String>
* @return String
*/
public String sendPostRequest(WebClient webClient, String url, Map<String, String> params) { // POST request
String body;
try {
body = mapper.writeValueAsString(params);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
URI uri = UriComponentsBuilder
.fromUriString(url)
.build().toUri();
Mono<String> mono = webClient.post()
.uri(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(body))
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Bad Request - Error Code " + response.statusCode().value())))
.onStatus(HttpStatusCode::is5xxServerError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Internal Server Error - Error Code " + response.statusCode().value())))
.bodyToMono(String.class);
return mono.block();
}
}
๐โ sendGetRequest
๋ธ๋กํน ๋ฐฉ์์ผ๋ก GET ์์ฒญ์ ์ฒ๋ฆฌ ํ ๋ฌธ์์ด๋ก ์๋ต์ ๋ฐํํ๋ค.
public String sendGetRequest(WebClient webClient, String url, Map<String, String> params) { // GET request
// MultiValueMap ์์ฑ
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.setAll(params); // ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ์ฒ๋ฆฌ๋ฅผ ์ํด, Map ์ MultiValueMap ์ผ๋ก ๋ณํ
// URI ๊ตฌ์ฑ
URI uri = UriComponentsBuilder
.fromUriString(url) // ๊ธฐ๋ณธ URL ์ค์
.queryParams(queryParams) // ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ์ถ๊ฐ
.build().toUri(); // URI ๊ฐ์ฒด๋ก ๋ฐํ
// WebClient ์์ฒญ ๊ตฌ์ฑ
Mono<String> mono = webClient.get() // GET ๋ฉ์๋ ์ง์
.uri(uri) // ์์ฒญ URI ์ค์
.accept(MediaType.APPLICATION_JSON) // Accept ํค๋ ์ค์
.retrieve() // ์๋ต ์ถ์ถ ์์
.onStatus(HttpStatusCode::is4xxClientError, response -> // 4xx ์๋ฌ ์ฒ๋ฆฌ
Mono.error(new RuntimeException("URL : " + url + "] Bad Request - Error Code " + response.statusCode().value())))
.onStatus(HttpStatusCode::is5xxServerError, response -> // 5xx ์๋ฌ ์ฒ๋ฆฌ
Mono.error(new RuntimeException("URL : " + url + "] Internal Server Error - Error Code " + response.statusCode().value())))
.bodyToMono(String.class); // ์๋ต ๋ณธ๋ฌธ์ String ์ผ๋ก ๋ณํ
return mono.block(); // ๋น๋๊ธฐ ์คํ์ ๋ธ๋กํนํ์ฌ ๊ฒฐ๊ณผ ๋ฐํ
}
์๋ต์ ์ถ์ถ ์, retrieve ์ exchange ์ฒ๋ฆฌ ๋ฐฉ์์ ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉฐ, ๊ฐ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ฐจ์ด์ ์ ์๋์ ๊ฐ๋ค.
๐ retrieve
- ๊ฐ๋จํ ์๋ต ์ฒ๋ฆฌ์ ์ ํฉํ๋ฉฐ ์๋ต ๋ณธ๋ฌธ๋ง ์ ๊ทผ ๊ฐ๋ฅ
- ๊ฐ๋จํ๊ณ ์์ ํ API ๊ตฌ์ฑ์ ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ์์ ์
๐ exchange
- ์ ์ฒด ClientResponse ์ ์ ๊ทผ ๊ฐ๋ฅ
- ์๋ต ํค๋, ์ฟ ํค ๋ฑ ์ธ๋ถ ์ ๋ณด ์ ๊ทผ ๊ฐ๋ฅ
- ์๋์ผ๋ก ๋ฆฌ์์ค ํด์ ํ์
- ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ๋ฅ์ฑ ์กด์ฌ
์ผ๋ฐ์ ์ผ๋ก retrieve ์ฌ์ฉ์ ์ฐ์ ๊ณ ๋ คํ๊ณ , ํ์ํ ๊ฒฝ์ฐ์๋ง exchange ๋ฅผ ์ฌ์ฉํ๋ค.
exchange ์ฌ์ฉ ์, ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง๋ฅผ ์ํด ํญ์ ์๋ต์ ์๋นํ๊ณ doFinally ๋ฑ์ ์ฌ์ฉํ์ฌ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ์ฌ์ผํ๋ค.
โ๐ sendPostRequest
๋ธ๋กํน ๋ฐฉ์์ผ๋ก POST ์์ฒญ์ ์ฒ๋ฆฌ ํ ๋ฌธ์์ด๋ก ์๋ต์ ๋ฐํํ๋ค.
public String sendPostRequest(WebClient webClient, String url, Map<String, String> params) { // POST request
// ์์ฒญ ๋ฐ๋ ์์ฑ
String body;
try {
body = mapper.writeValueAsString(params); // Map ์ JSON ๋ฌธ์์ด๋ก ๋ณํ
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
// URI ๊ตฌ์ฑ
URI uri = UriComponentsBuilder
.fromUriString(url) // ๊ธฐ๋ณธ URL ์ค์
.build().toUri(); // URI ๊ฐ์ฒด๋ก ๋ณํ
Mono<String> mono = webClient.post() // POST ๋ฉ์๋ ์ง์
.uri(uri) // ์์ฒญ URI ์ค์
.contentType(MediaType.APPLICATION_JSON) // Content-Type ํค๋ ์ค์
.accept(MediaType.APPLICATION_JSON) // Accept ํค๋ ์ค์
.body(BodyInserters.fromValue(body)) // ์์ฒญ ๋ฐ๋ ์ค์
.retrieve() // ์๋ต ์ถ์ถ ์์
.onStatus(HttpStatusCode::is4xxClientError, response -> // 4xx ์๋ฌ ์ฒ๋ฆฌ
Mono.error(new RuntimeException("URL : " + url + "] Bad Request - Error Code " + response.statusCode().value())))
.onStatus(HttpStatusCode::is5xxServerError, response -> // 5xx ์๋ฌ ์ฒ๋ฆฌ
Mono.error(new RuntimeException("URL : " + url + "] Internal Server Error - Error Code " + response.statusCode().value())))
.bodyToMono(String.class); // ์๋ต ๋ณธ๋ฌธ์ String ์ผ๋ก ๋ณํ
return mono.block(); // ๋น๋๊ธฐ ์คํ์ ๋ธ๋กํนํ์ฌ ๊ฒฐ๊ณผ ๋ฐํ
}
๋ ผ ๋ธ๋กํน ๋ฐฉ์์ ๋ํ ํธ๋ค๋ง์ ๊ฒฝ์ฐ, ๋ฉ์๋์ ๋ฐํ ํ์ ์ String ์์ Mono<String> ์ผ๋ก ๋ณ๊ฒฝํ๊ณ ,
๋ฐํ ์ block ์ ์ํํ์ง ์๊ณ Mono ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
// ๋ฐํ ํ์
์ Mono<String> ์ผ๋ก ์ง์
public Mono<String> sendNonBlockingRequest(WebClient webClient, String url, Map<String, String> params) { // POST request
String body;
try {
body = mapper.writeValueAsString(params);
} catch (JsonProcessingException e) {
return Mono.error(e); // ์๋ฌ ์ฒ๋ฆฌ๋ฅผ Mono ๋ก ๊ฐ์ธ ๋ฐํ
}
URI uri = UriComponentsBuilder
.fromUriString(url)
.build().toUri();
return webClient.post() // ์๋ต Mono ๊ฐ์ฒด๋ฅผ ๋ฐ๋ก ๋ฐํ
.uri(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(body))
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Bad Request - Error Code " + response.statusCode().value())))
.onStatus(HttpStatusCode::is5xxServerError, response ->
Mono.error(new RuntimeException("URL : " + url + "] Internal Server Error - Error Code " + response.statusCode().value())))
.bodyToMono(String.class);
}
๐ RequestService
๋ค์์ ์ง์ ํ ์์ฒญ ์ฒ๋ฆฌ ๋ฐฉ์์ ๋ฐ๋ผ RequestHandler ๋ฅผ ํตํด ์๋ต์ ๋ฐ๊ณ ,
๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํ ๋ฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณต ๋ฐ ์์ธ ์ฒ๋ฆฌํ๋ ๋น์ฆ๋์ค ๊ณ์ธต ์๋น์ค๋จ์ ์์ฑํ๋ค.
@Service
@Slf4j
@RequiredArgsConstructor
public class RequestService {
private final RequestHandler requestHandler; // ์์ฒญ ํธ๋ค๋ง ์๋น์ค
private final WebClientFactory webClientFactory; // WebClient ๋ฐํ ํฉํ ๋ฆฌ
private final UtilService utilService; // ์์์ฉ ๊ฐ์ ์๋น์ค
/**
* ๋ธ๋กํน POST ์์ฒญ
*
* @param url String
* @param params Map<String, String>
* @return String
*/
public String blockRequest(String url, Map<String, String> params) {
return requestHandler.sendPostRequest(webClientFactory.getWebClient(), url, params);
}
/**
* ๋
ผ ๋ธ๋กํน Mono POST ์์ฒญ
*
* @param url String
* @param params Map<String, String>
* @return Mono<String>
*/
public Mono<String> monoRequest(String url, Map<String, String> params) {
return requestHandler.sendNonBlockPostRequest(webClientFactory.getWebClient(), url, params)
.flatMap(this::parseResponse) // ๋ฉ์๋ ์ฒด์ด๋ ๊ฐ๋ฅ
.doOnSuccess(resp -> log.info("process completed: {}", resp))
.doOnError(error -> log.info("error occurred: {}", error.getMessage()));
}
/**
* ๋
ผ ๋ธ๋กํน Subscribe POST ์์ฒญ
*
* @param url String
* @param params Map<String, String>
*/
public void subscribeRequest(String url, Map<String, String> params) {
requestHandler.sendNonBlockPostRequest(webClientFactory.getWebClient(), url, params)
.subscribe(
resp -> log.info("process completed: {}", resp),
error -> log.error("error occurred: {}", error.getMessage())
);
}
/**
* ์๋ต ์ฒ๋ฆฌ ๋ณด์กฐ ๋ฉ์๋
*
* @param resp String
* @return Mono<String>
*/
private Mono<String> parseResponse(String resp) {
return utilService.parseResponse(resp) // ํด๋น ๋ฉ์๋๋ Mono.just(String) ๋ก ๋ฐํ
.doOnSuccess(result -> log.info("data parsing completed: {}", result))
.doOnError(error -> log.error("data parsing failed: {}", error.getMessage()));
}
}
์ ํด๋์ค์ ๋ฉ์๋์ค ๋ ผ ๋ธ๋กํน ๋ฐฉ์ ๋ฉ์๋์ ๋ฐํ ํ์ ์ผ๋ก Mono ์ subscribe() ๋ ์ข ๋ฅ๋ฅผ ๋ช ์ํ์๋ค.
๊ฐ ๋ฐํ ํ์ ์ ์ฐจ์ด์ ์ ์๋์ ๊ฐ๋ค.
๐ Mono(Flux) ๋ฐํ
- ์ง์ฐ ์คํ(lazy execution)
- ํ์ดํ๋ผ์ธ ๊ตฌ์ฑ ๊ฐ๋ฅ
- ๋ค๋ฅธ ๋ฆฌ์กํฐ๋ธ ์ฐ์ฐ์์ ์กฐํฉ ๊ฐ๋ฅ
- ์คํ๋ง WebFlux ์ ํตํฉ ์ฉ์ด
๐ subscribe()
- ์ฆ์ ์คํ๋จ
- void ๋ฐํ
- ๋ฉ์๋ ๋ด์์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋จ
- ์ฃผ๋ก ์ต์ข ์๋น์์์ ์ฌ์ฉ
๐ RequestController
๋์ผ๋ก ํด๋ผ์ด์ธํธ์ HTTP ์์ฒญ์ ๋ฐ์๋ค์ด๋ ์ค๋ํฌ์ธํธ๋ฅผ ์ ๊ณตํ๋ ํํ ๊ณ์ธต ์ปจํธ๋กค๋ฌ๋จ์ ์์ฑํ๋ค.
@RequestMapping("/api")
@RestController
@RequiredArgsConstructor
public class RequestController {
private final RequestService requestService;
/**
* ๋ธ๋กํน POST ์์ฒญ
*
* @param req RequestDTO
* @return String
*/
@PostMapping("/post/block")
public String blockRequest(@RequestBody RequestDTO req) {
return requestService.blockRequest(req.getUrl(), req.getParams());
}
/**
* ๋
ผ ๋ธ๋กํน Mono POST ์์ฒญ
*
* @param req RequestBody
* @return Mono<String>
*/
@PostMapping("/post/mono")
public Mono<String> monoRequest(@RequestBody RequestDTO req) {
return requestService.monoRequest(req.getUrl(), req.getParams());
}
/**
* ๋
ผ ๋ธ๋กํน Subscribe POST ์์ฒญ
*
* @param req RequestBody
* @return Mono<String>
*/
@PostMapping("/post/subscribe")
public ResponseEntity<Void> subscribeRequest(@RequestBody RequestDTO req) {
requestService.subscribeRequest(req.getUrl(), req.getParams());
return ResponseEntity.accepted().build(); // 202 Accepted, ์์ฒญ์ ์ ์๋์์ผ๋, ์ฒ๋ฆฌ๋ ์ถํ์ ์๋ฃ
}
}
๋ง์น๋ฉฐ
์ด์ฒ๋ผ WebClient ๋ ๋น๋๊ธฐ ๋ฐฉ์์ HTTP ์์ฒญ์ ๋ณด๋ค ์ ์ฐํ๊ณ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ๋์์ค๋ค.
์์ ํ ๊ธฐ๋ฅ์ธ์๋ retry, filter, ๋ ๊ตฌ์ฒด์ ์ธ ์๋ฌ ํธ๋ค๋ง ๊ธฐ๋ฅ๋ค์ ํ์ฉํ๋ฉด,
๋ ๋์ ํ์ฅ์ฑ๊ณผ ์์ ์ฑ์ ๊ฐ์ถ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ์ถํ ์ ์๋ค.
'Web Development > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] WebClient - 1. ๊ฐ๋ ๋ฐ ๊ธฐ๋ณธ ์ค์ (0) | 2025.02.26 |
---|---|
[Spring] MultipartFille ์ ํ์ฉํ ๋ค์ค ํ์ผ ์ ๋ก๋ ๋ฐ ์ถ๋ ฅ (0) | 2022.06.30 |
[Spring] FlashMap ์ ํ์ฉํ์ฌ Controller ๊ฐ ๊ฐ ์ ๋ฌ (0) | 2022.06.29 |
๋๊ธ