본문 바로가기

Web/spring

[Spring Framework integration] 1. REST Clients

728x90

 

Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-client-access

 

Integration

The Spring Framework provides abstractions for the asynchronous execution and scheduling of tasks with the TaskExecutor and TaskScheduler interfaces, respectively. Spring also features implementations of those interfaces that support thread pools or delega

docs.spring.io

 

내용에 문제가 있을 경우 알려주세요 감사합니다😃


 

1. REST Clients

Spring Framework 는 end point 호출을 위해 세가지 선택사항을 제공한다

 

  • WebClient - non-blocking, reactive client w fluent API.
  • RestTemplate - synchronous client with template method API.
  • HTTP Interface - annotated interface with generated, dynamic proxy implementation.

 

 

 

1.1. WebClient

HTTP Request 를 수행하기 위한 non-blocking, reactive client이다

5.0에서 도입되었으며, synchronous, streaming scenarios 를 지원하는 RestTemplate 대안을 제공한다

 

WebClient supports the following:

  • Non-blocking I/O.
  • Reactive Streams back pressure.
  • High concurrency with fewer hardware resources.
  • Functional-style, fluent API that takes advantage of Java 8 lambdas.
  • Synchronous and asynchronous interactions.
  • Streaming up to or streaming down from a server.

 

1.2. RestTemplate

이거 말고 WebClient를 쓰는게 좋다

RestTemplate는 HTTP Client library를 이용하여 더 높은 수준의 API를 제공한다

한 줄만으로 REST endpoint 를 쉽게 호출할 수 있다

 

Table 1. RestTemplate methodsMethod groupDescription

getForObject Retrieves a representation via GET.
getForEntity Retrieves a ResponseEntity (that is, status, headers, and body) by using GET.
headForHeaders Retrieves all headers for a resource by using HEAD.
postForLocation Creates a new resource by using POST and returns the Location header from the response.
postForObject Creates a new resource by using POST and returns the representation from the response.
postForEntity Creates a new resource by using POST and returns the representation from the response.
put Creates or updates a resource by using PUT.
patchForObject Updates a resource by using PATCH and returns the representation from the response. Note that the JDK HttpURLConnection does not support PATCH, but Apache HttpComponents and others do.
delete Deletes the resources at the specified URI by using DELETE.
optionsForAllow Retrieves allowed HTTP methods for a resource by using ALLOW.
exchange More generalized (and less opinionated) version of the preceding methods that provides extra flexibility when needed. It accepts a RequestEntity (including HTTP method, URL, headers, and body as input) and returns a ResponseEntity.
These methods allow the use of ParameterizedTypeReference instead of Class to specify a response type with generics.
execute The most generalized way to perform a request, with full control over request preparation and response extraction through callback interfaces.

 

 

1.2.1. Initialization

기본 생성자는 java.net.HttpURLConnection 에서 요청을 수행한다. ClientHttpRequestFactory를 구현하여 다른 HTTP 라이브러리로 전환할 수 있다.

 

기본 제공하는 built-in

  • Apache HttpComponents
  • Netty
  • OkHttp

OkHttp 앱 할때 진짜 많이 씀

반갑군 ㅎ_ㅎ

 

예를 들어서 Apache HttpComponents를 쓰려면 이렇게 쓴다

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

 

각 ClientHttpRequestFactory 는 credentials, connection pooling, 기타 세부정보와 같이 기본 HTTP Client library와 관련된 구성 옵션을 보여준다

 

HTTP 요청에 대한 java.net 구현은 오류(예: 401)를 나타내는 응답 상태에 액세스할 때 예외를 발생시킬 수 있다. 이런 문제의 경우 다른 HTTP 클라이언트 라이브러리로 전환하면 된다

 

 

 

RestTemplate는 metrics 및 traces 를 생성을 확인하기 위해 instrumented 할 수 있다.

https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#integration.observability.http-client.resttemplate

 

Integration

The Spring Framework provides abstractions for the asynchronous execution and scheduling of tasks with the TaskExecutor and TaskScheduler interfaces, respectively. Spring also features implementations of those interfaces that support thread pools or delega

docs.spring.io

 

 

 

 

 

URIs

많은 RestTemplate method는 URI 템플릿과 URI template variables를 String, 또는 Map<String, String> 으로 허용한다

 

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

 

 

URI Template는 자동 인코딩 된다

restTemplate.getForObject("https://example.com/hotel list", String.class);

// Results in request to "https://example.com/hotel%20list"

 

RestTemplate의 uriTemplateHandler 속성을 사용하여 URI 인코딩 방법을 사용자 지정할 수 있다.

또 java.net.URI를 준비하고 URI를 허용하는 RestTemplate 메서드 중 하나로 전달할 수도 있다

 

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-uri-building

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonl

docs.spring.io

 

 

 

 

Headers

exchange() method를 사용하여 request headers를 정의할 수 있다

String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
        .header("MyRequestHeader", "MyValue")
        .build();

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

You can obtain response headers through many RestTemplate method variants that return ResponseEntity.

 

 

 

1.2.2. Body

RestTemplate method로 전달되어 반환된 객체는 HttpMessageConverter의 도움받아 converted 된다

 

 

On a POST, an input object is serialized to the request body

URI location = template.postForLocation("https://example.com/people", person);

Content-Type 요청 Header는 명시적으로 선언해주지않아도 된다

대부분 Object type 에 따라 호환되는 message converter 가 있고 그에 따라 컨텐츠 유형을 설정해준다

exchange 가 필요한 경우 명시적으로 제공해줄 수 있다

 

GET response body는 이렇게 온다

Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);

Accept 또한 요청 헤더에서 명시적 선언이필요없다

위와 같은 이유임

 

 

Message Conversion

Table 2. HttpMessageConverter ImplementationsMessageConverterDescription
StringHttpMessageConverter An HttpMessageConverter implementation that can read and write String instances from the HTTP request and response. By default, this converter supports all text media types (text/*) and writes with a Content-Type of text/plain.
FormHttpMessageConverter An HttpMessageConverter implementation that can read and write form data from the HTTP request and response. By default, this converter reads and writes the application/x-www-form-urlencoded media type. Form data is read from and written into a MultiValueMap<String, String>. The converter can also write (but not read) multipart data read from a MultiValueMap<String, Object>. By default, multipart/form-data is supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for writing form data. Consult the javadoc for FormHttpMessageConverter for further details.
ByteArrayHttpMessageConverter An HttpMessageConverter implementation that can read and write byte arrays from the HTTP request and response. By default, this converter supports all media types (*/*) and writes with a Content-Type of application/octet-stream. You can override this by setting the supportedMediaTypes property and overriding getContentType(byte[]).
MarshallingHttpMessageConverter An HttpMessageConverter implementation that can read and write XML by using Spring’s Marshaller and Unmarshaller abstractions from the org.springframework.oxm package. This converter requires a Marshaller and Unmarshaller before it can be used. You can inject these through constructor or bean properties. By default, this converter supports text/xml and application/xml.
MappingJackson2HttpMessageConverter An HttpMessageConverter implementation that can read and write JSON by using Jackson’s ObjectMapper. You can customize JSON mapping as needed through the use of Jackson’s provided annotations. When you need further control (for cases where custom JSON serializers/deserializers need to be provided for specific types), you can inject a custom ObjectMapper through the ObjectMapper property. By default, this converter supports application/json.
MappingJackson2XmlHttpMessageConverter An HttpMessageConverter implementation that can read and write XML by using Jackson XML extension’s XmlMapper. You can customize XML mapping as needed through the use of JAXB or Jackson’s provided annotations. When you need further control (for cases where custom XML serializers/deserializers need to be provided for specific types), you can inject a custom XmlMapper through the ObjectMapper property. By default, this converter supports application/xml.
SourceHttpMessageConverter An HttpMessageConverter implementation that can read and write javax.xml.transform.Source from the HTTP request and response. Only DOMSource, SAXSource, and StreamSource are supported. By default, this converter supports text/xml and application/xml.
BufferedImageHttpMessageConverter An HttpMessageConverter implementation that can read and write java.awt.image.BufferedImage from the HTTP request and response. This converter reads and writes the media type supported by the Java I/O API.

 

 

1.2.3. Jackson JSON Views

https://www.baeldung.com/jackson-json-view-annotation

MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
    RequestEntity.post(new URI("https://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

 

 

 

1.2.4. Multipart

multipart data를 보낼 때는 부분 컨텐츠일 경우 Object, 파일 부분일 경우 Resource, 헤더가 있는 컨텐츠 부분일 경우 HttpEntity일 수 있다. 값이 >> MultiValueMap<String, Object> 

 

 

MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();

parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));

 

이 또한 Content-Type 을 지정할 필요가 없다

컨텐츠 형식은 직렬화로 선택한 선택한 HttpMessageConverter 또는 파일 확장명을 기반으로 하는 리소스의 경우에 따라 자동으로 결정된다

필요할 경우에는 MediaType에 HttpEntity wrapper을 명시적 제공하면 된다

 

MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);

MultiValueMap에 문자열이 아닌 값이 하나 이상 포함된 경우 

Content-Type은 FormHttpMessageConverter에 의해 multipart/form-data로 설정된다. 

MultiValueMap에 문자열 값이 있는 경우 Content-Type은 기본적으로 application/x-www-form-urlencoded로 설정된다.

필요한 경우 Content-Type을 명시적으로 설정하면 된다

 

 

 

 

1.3. HTTP Interface

Spring Framework를 사용하면 HTTP exchanges 를 위한 애노테이션이 있는 메서드를 사용하여 HTTP 서비스를 Java 인터페이스로 정의할 수 있다.

그리고 이 인터페이스를 구현하고 교환을 수행하는 proxy를 생성할 수 있습니다.

이는 기본 HTTP 클라이언트 사용에 대한 세부 정보를 wrapping하는 facade가 자주 포함되는 HTTP 원격 액세스를 단순화하게 돕는다

 

 

1. method에 @HttpExchange 인터페이스를 선언함

interface RepositoryService {

    @GetExchange("/repos/{owner}/{repo}")
    Repository getRepository(@PathVariable String owner, @PathVariable String repo);

    // more HTTP exchange methods...

}

 

2. declared HTTP exchanges 를 수행할 proxy 생성

WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();

RepositoryService service = factory.createClient(RepositoryService.class);

 

 

3. @HttpExchange 는 모든 type에서 지원된다

@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {

    @GetExchange
    Repository getRepository(@PathVariable String owner, @PathVariable String repo);

    @PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    void updateRepository(@PathVariable String owner, @PathVariable String repo,
            @RequestParam String name, @RequestParam String description, @RequestParam String homepage);

}

 

 

 

1.3.1. Method Parameters

Annotated, HTTP exchange methods support flexible method signatures with the following method parameters:

Method argumentDescription
URI Dynamically set the URL for the request, overriding the annotation’s url attribute.
HttpMethod Dynamically set the HTTP method for the request, overriding the annotation’s method attribute
@RequestHeader Add a request header or multiple headers. The argument may be a Map<String, ?> or MultiValueMap<String, ?> with multiple headers, a Collection<?> of values, or an individual value. Type conversion is supported for non-String values.
@PathVariable Add a variable for expand a placeholder in the request URL. The argument may be a Map<String, ?> with multiple variables, or an individual value. Type conversion is supported for non-String values.
@RequestBody Provide the body of the request either as an Object to be serialized, or a Reactive Streams Publisher such as Mono, Flux, or any other async type supported through the configured ReactiveAdapterRegistry.
@RequestParam Add a request parameter or multiple parameters. The argument may be a Map<String, ?> or MultiValueMap<String, ?> with multiple parameters, a Collection<?> of values, or an individual value. Type conversion is supported for non-String values.
When "content-type" is set to "application/x-www-form-urlencoded", request parameters are encoded in the request body. Otherwise, they are added as URL query parameters.
@RequestPart Add a request part, which may be a String (form field), Resource (file part), Object (entity to be encoded, e.g. as JSON), HttpEntity (part content and headers), a Spring Part, or Reactive Streams Publisher of any of the above.
@CookieValue Add a cookie or multiple cookies. The argument may be a Map<String, ?> or MultiValueMap<String, ?> with multiple cookies, a Collection<?> of values, or an individual value. Type conversion is supported for non-String values.

 

 

 

1.3.2. Return Values

Annotated, HTTP exchange methods support the following return values:

Method return valueDescription
void, Mono<Void> Perform the given request, and release the response content, if any.
HttpHeaders, Mono<HttpHeaders> Perform the given request, release the response content, if any, and return the response headers.
<T>, Mono<T> Perform the given request and decode the response content to the declared return type.
<T>, Flux<T> Perform the given request and decode the response content to a stream of the declared element type.
ResponseEntity<Void>, Mono<ResponseEntity<Void>> Perform the given request, and release the response content, if any, and return a ResponseEntity with the status and headers.
ResponseEntity<T>, Mono<ResponseEntity<T>> Perform the given request, decode the response content to the declared return type, and return a ResponseEntity with the status, headers, and the decoded body.
Mono<ResponseEntity<Flux<T>> Perform the given request, decode the response content to a stream of the declared element type, and return a ResponseEntity with the status, headers, and the decoded response body stream.

 

ReactiveAdapterRegistry 에 등록된 비동기 또는 reactive type 사용도 가능하다

 

 

1.3.3. Exception Handling

기본적으로 WebClient는 4xx 및 5xx HTTP 상태 코드에 대해 WebClientResponseException을 발생시킨다.

이를 커스텀하려면 클라이언트를 통해 수행되는 모든 응답에 적용되는 response status handler를 등록할 수 있다

 

WebClient webClient = WebClient.builder()
        .defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
        .build();

WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
        .builder(clientAdapter).build();

For more details and options, such as suppressing error status codes, see the Javadoc of defaultStatusHandler in WebClient.Builder.

728x90