2.4. Request Body

Request Body๋Š” Mono ๋˜๋Š” ์ฝ”ํ‹€๋ฆฐ ์ฝ”๋ฃจํ‹ด Deferred์™€ ๊ฐ™์ด ReactiveAdapterRegistry๊ฐ€ ํ•ธ๋“ค๋งํ•˜๋Š” ๋ชจ๋“  ๋น„๋™๊ธฐ ํƒ€์ž…์œผ๋กœ๋ถ€ํ„ฐ ์ธ์ฝ”๋”ฉ๋  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜๋Š” ๊ทธ ์˜ˆ์ œ๋‹ค:

Java:

Mono<Person> personMono = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(personMono, Person.class)
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

val personDeferred: Deferred<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body<Person>(personDeferred)
        .retrieve()
        .awaitBody<Unit>()

๋˜ํ•œ ๋‹ค์Œ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๊ฐ์ฒด ์Šคํฌ๋ฆผ์„ ์ธ์ฝ”๋”ฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค:

Java:

Flux<Person> personFlux = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_STREAM_JSON)
        .body(personFlux, Person.class)
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

val people: Flow<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(people)
        .retrieve()
        .awaitBody<Unit>()

๋˜๋Š” ์‹ค์ œ ๊ฐ’์„ ๊ฐ€์ง„ ๊ฒฝ์šฐ์—๋Š” bodyValue ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Java:

Person person = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

val person: Person = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .awaitBody<Unit>()


2.4.1. ํผ ๋ฐ์ดํ„ฐ(Form Data)

ํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด, MultiValueMap<String, String>์„ body๋กœ ์‚ฌ์šฉํ•œ๋‹ค. FormHttpMessageWriter์— ์˜ํ•ด์„œ ์ž๋™์œผ๋กœ application/x-www-form-urlencoded๋กœ ์ฝ˜ํ…์ธ  ํƒ€์ž…์ด ์„ค์ •๋œ๋‹ค. ๋‹ค์Œ MultiValueMap<String, String>์„ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ๋‹ค:

Java:

MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

val formData: MultiValueMap<String, String> = ...

client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .awaitBody<Unit>()

BodyInserters๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๋ผ์ธ ํผ(form) ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์Œ์€ ๊ทธ ์˜ˆ์ œ๋‹ค:

Java:

import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .awaitBody<Unit>()


2.4.2. ๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฐ์ดํ„ฐ(Multipart Data)

๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ ค๋ฉด, ๊ฐ’(value)์ด ํŒŒํŠธ(part) ์ปจํ…์ธ ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Object ๋˜๋Š” ํŒŒํŠธ์˜ ์ปจํ…์ธ ์™€ ํ—ค๋”๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” HttpEntity ์ธ์Šคํ„ด์Šค์ธ MultiValueMap<String, ?>๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. MultipartBodyBuilder๋Š” ๋ฉ€ํ‹ฐํŒŒํŠธ ์š”์ฒญ์„ ์ค€๋น„ํ•˜๊ธฐ ์œ„ํ•œ ํŽธ๋ฆฌํ•œ API๋ฅผ ์ œ๊ณตํ•œ๋‹ค ๋‹ค์Œ์€ MultiValueMap<String, ?>๋ฅผ ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•˜๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์ œ๋‹ค:

Java:

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

Kotlin:

val builder = MultipartBodyBuilder().apply {
    part("fieldPart", "fieldValue")
    part("filePart1", new FileSystemResource("...logo.png"))
    part("jsonPart", new Person("Jason"))
    part("myPart", part) // Part from a server request
}

val parts = builder.build()

๋Œ€๋ถ€๋ถ„ ๊ฐ ํŒŒํŠธ๋งˆ๋‹ค Content-Type์„ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ์ฝ˜ํ…์ธ  ํƒ€์ž…์€ ์ง๋ ฌํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์„ ํƒํ•œ HttpMessageWriter๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋˜๋Š” Resource์˜ ๊ฒฝ์šฐ ํŒŒ์ผ ํ™•์žฅ์ž์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ž๋™์œผ๋กœ ๊ฒฐ์ •๋œ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด, ์˜ค๋ฒ„๋กœ๋”ฉ๋œ ๋นŒ๋” part ๋ฉ”์„œ๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ํ†ตํ•ด์„œ ๊ฐ ํŒŒํŠธ์—์„œ ์‚ฌ์šฉํ•  MediaType๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

MultiValueMap์ด ์ค€๋น„๋๋‹ค๋ฉด, WebClient์— ์ „๋‹ฌํ•˜๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ body ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹ค์Œ์€ ๊ทธ ์˜ˆ์ œ๋‹ค:

Java:

MultipartBodyBuilder builder = ...;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

val builder: MultipartBodyBuilder = ...

client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .awaitBody<Unit>()

MultiValueMap์— ์ผ๋ฐ˜์ ์ธ ํผ ๋ฐ์ดํ„ฐ(application/x-www-form-urlencoded)๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฐ’(non-String)์ด ํ•˜๋‚˜๋ผ๋„ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, Content-Type์„ multipart/form-data๋กœ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. MultipartBodyBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ•ญ์ƒ HttpEntity๋กœ ๋ž˜ํ•‘ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

MultipartBodyBuilder์˜ ๋Œ€์•ˆ์œผ๋กœ ๋‚ด์žฅ BodyInserters๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๋ผ์ธ ์Šคํƒ€์•Œ์˜ ๋ฉ€ํ‹ฐํŒŒํŠธ ์ฝ˜ํ…์ธ ๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค. ๋‹ค์Œ์€ ๊ทธ ์˜ˆ์ œ๋‹ค:

Java:

import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .bodyToMono(Void.class);

Kotlin:

import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .awaitBody<Unit>()

๋ชฉ์ฐจ ๊ฐ€์ด๋“œ