URI Links

Dev.Hammy·2024년 4월 6일
0

반응형 스택에서 이에 상응하는 내용 보기

이 섹션에서는 URI 작업을 위해 Spring Framework에서 사용할 수 있는 다양한 옵션을 설명합니다.

UriComponents

스프링 MVC와 스프링 WebFlux UriComponentsBuilder는 다음 예제와 같이 변수가 있는 URI 템플릿에서 URI를 빌드하는 데 도움이 됩니다.

UriComponents uriComponents = UriComponentsBuilder
		.fromUriString("https://example.com/hotels/{hotel}")  // (1)
		.queryParam("q", "{q}") // (2)
		.encode()  // (3)
		.build();   // (4)

URI uri = uriComponents.expand("Westin", "123").toUri();  // (5)

(1) URI 템플릿을 사용하는 정적 팩터리 메서드입니다.
(2) URI 구성 요소를 추가하거나 교체합니다.
(3) URI 템플릿과 URI 변수를 인코딩하도록 요청합니다.
(4) UriComponents를 빌드합니다.
(5) 변수를 확장하고 URI를 얻습니다.

다음 예제와 같이 이전 예제를 하나의 체인으로 통합하고 buildAndExpand를 사용하여 단축할 수 있습니다.

URI uri = UriComponentsBuilder
		.fromUriString("https://example.com/hotels/{hotel}")
		.queryParam("q", "{q}")
		.encode()
		.buildAndExpand("Westin", "123")
		.toUri();

다음 예제와 같이 URI(인코딩을 의미함)로 직접 이동하여 더 단축할 수 있습니다.

URI uri = UriComponentsBuilder
		.fromUriString("https://example.com/hotels/{hotel}")
		.queryParam("q", "{q}")
		.build("Westin", "123");

다음 예제와 같이 전체 URI 템플릿을 사용하면 더 단축할 수 있습니다.

URI uri = UriComponentsBuilder
		.fromUriString("https://example.com/hotels/{hotel}?q={q}")
		.build("Westin", "123");

UriBuilder

스프링 MVC와 스프링 WebFlux

UriComponentsBuilderUriBuilder를 구현합니다. UriBuilderFactory를 사용하여 UriBuilder를 생성할 수 있습니다. UriBuilderFactoryUriBuilder는 함께 기본 URL, 인코딩 기본 설정 및 기타 세부 정보와 같은 공유 구성을 기반으로 URI 템플릿에서 URI를 구축하는 플러그형 메커니즘을 제공합니다.

UriBuilderFactory를 사용하여 RestTemplateWebClient를 구성하여 URI 준비를 사용자 지정할 수 있습니다. DefaultUriBuilderFactoryUriComponentsBuilder를 내부적으로 사용하고 공유 구성 옵션을 노출하는 UriBuilderFactory의 기본 구현입니다.

다음 예에서는 RestTemplate을 구성하는 방법을 보여줍니다.

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

다음 예에서는 WebClient를 구성합니다.

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

또한 DefaultUriBuilderFactory를 직접 사용할 수도 있습니다. 이는 UriComponentsBuilder를 사용하는 것과 유사하지만 정적 팩터리 메서드 대신 다음 예제와 같이 구성 및 기본 설정을 보유하는 실제 인스턴스입니다.

String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
		.queryParam("q", "{q}")
		.build("Westin", "123");

URI Encoding

스프링 MVC와 스프링 WebFlux

UriComponentsBuilder는 두 가지 수준에서 인코딩 옵션을 노출합니다.

  • UriComponentsBuilder#encode(): 먼저 URI 템플릿을 사전 인코딩한 다음 확장 시 URI 변수를 엄격하게 인코딩합니다.

  • UriComponents#encode(): URI 변수가 확장된 후 URI 구성 요소를 인코딩합니다.

두 옵션 모두 ASCII가 아닌 문자와 잘못된 문자를 이스케이프된 옥텟으로 바꿉니다. 그러나 첫 번째 옵션도 URI 변수에 나타나는 예약된 의미로 문자를 바꿉니다.

[Tip]
경로에서는 유효하지만 의미가 보존되어 있는 ";"을 고려하세요. 첫 번째 옵션은 ";"을 대체합니다. URI 변수에는 "%3B"가 있지만 URI 템플릿에는 없습니다. 대조적으로, 두 번째 옵션은 경로에서 유효한 문자이기 때문에 ";"를 대체하지 않습니다.

대부분의 경우 첫 번째 옵션은 URI 변수를 완전히 인코딩할 불투명 데이터로 처리하므로 예상한 결과를 제공할 가능성이 높으며, 두 번째 옵션은 URI 변수에 의도적으로 예약된 문자가 포함된 경우 유용합니다. 두 번째 옵션은 URI 변수처럼 보이는 모든 항목도 인코딩하므로 URI 변수를 전혀 확장하지 않을 때도 유용합니다.

다음 예에서는 첫 번째 옵션을 사용합니다.

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
		.queryParam("q", "{q}")
		.encode()
		.buildAndExpand("New York", "foo+bar")
		.toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

다음 예제와 같이 URI(인코딩을 의미함)로 직접 이동하여 앞의 예제를 단축할 수 있습니다.

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
		.queryParam("q", "{q}")
		.build("New York", "foo+bar");

다음 예제와 같이 전체 URI 템플릿을 사용하면 더 단축할 수 있습니다.

URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
		.build("New York", "foo+bar");

WebClientRestTemplateUriBuilderFactory 전략을 통해 내부적으로 URI 템플릿을 확장하고 인코딩합니다. 다음 예와 같이 둘 다 사용자 지정 전략으로 구성할 수 있습니다.

String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

DefaultUriBuilderFactory 구현은 내부적으로 UriComponentsBuilder를 사용하여 URI 템플릿을 확장하고 인코딩합니다. 팩토리로서 아래 인코딩 모드 중 하나를 기반으로 인코딩 접근 방식을 구성할 수 있는 단일 위치를 제공합니다.

  • TEMPLATE_AND_VALUES: 이전 목록의 첫 번째 옵션에 해당하는 UriComponentsBuilder#encode()를 사용하여 URI 템플릿을 사전 인코딩하고 확장 시 URI 변수를 엄격하게 인코딩합니다.

  • VALUES_ONLY: URI 템플릿을 인코딩하지 않고 대신 템플릿으로 확장하기 전에 UriUtils#encodeUriVariables를 통해 URI 변수에 엄격한 인코딩을 적용합니다.

  • URI_COMPONENT: 앞선 목록의 두 번째 옵션에 해당하는 UriComponents#encode()를 사용하여 URI 변수가 확장된 후 URI 구성 요소 값을 인코딩합니다.

  • NONE: 인코딩이 적용되지 않습니다.

  • RestTemplate은 역사적인 이유와 이전 버전과의 호환성을 위해 EncodingMode.URI_COMPONENT로 설정됩니다. WebClient는 5.0.x의 EncodingMode.URI_COMPONENT에서 5.1의 EncodingMode.TEMPLATE_AND_VALUES로 변경된 DefaultUriBuilderFactory의 기본값을 사용합니다.

Relative Servelet Requests

다음 예제와 같이 ServletUriComponentsBuilder를 사용하여 현재 요청과 관련된 URI를 생성할 수 있습니다.

HttpServletRequest request = ...

// Re-uses scheme, host, port, path, and query string...

URI uri = ServletUriComponentsBuilder.fromRequest(request)
		.replaceQueryParam("accountId", "{id}")
		.build("123");

다음 예제와 같이 컨텍스트 경로를 기준으로 URI를 만들 수 있습니다.

HttpServletRequest request = ...

// Re-uses scheme, host, port, and context path...

URI uri = ServletUriComponentsBuilder.fromContextPath(request)
		.path("/accounts")
		.build()
		.toUri();

다음 예제와 같이 서블릿에 상대적인 URI(예: /main/*)를 생성할 수 있습니다.

HttpServletRequest request = ...

// Re-uses scheme, host, port, context path, and Servlet mapping prefix...

URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
		.path("/accounts")
		.build()
		.toUri();

[Note]
5.1부터 ServletUriComponentsBuilder는 클라이언트 시작 주소를 지정하는 ForwardedX-Forwarded-* 헤더의 정보를 무시합니다. 이러한 헤더를 추출하여 사용하거나 삭제하려면 ForwardedHeaderFilter를 사용하는 것이 좋습니다.

Links to Controllers

Spring MVC는 컨트롤러 메소드에 대한 링크를 준비하는 메커니즘을 제공합니다. 예를 들어 다음 MVC 컨트롤러는 링크 생성을 허용합니다.

@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

	@GetMapping("/bookings/{booking}")
	public ModelAndView getBooking(@PathVariable Long booking) {
		// ...
	}
}

다음 예제와 같이 이름으로 메서드를 참조하여 링크를 준비할 수 있습니다.

UriComponents uriComponents = MvcUriComponentsBuilder
	.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

앞의 예에서는 경로 변수로 사용되고 URL에 삽입될 실제 메서드 인수 값(이 경우 긴 값: 21)을 제공합니다. 또한 유형 수준 요청 매핑에서 상속된 hotel 변수와 같은 나머지 URI 변수를 채우기 위해 값 42를 제공합니다. 메소드에 더 많은 인수가 있는 경우 URL에 필요하지 않은 인수에 대해 null을 제공할 수 있습니다. 일반적으로 @PathVariable@RequestParam 인수만 URL 구성에 관련됩니다.

MvcUriComponentsBuilder를 사용하는 추가 방법이 있습니다. 예를 들어, 다음 예제에서 볼 수 있듯이 컨트롤러 메서드를 이름으로 참조하지 않도록 프록시를 통한 모의 테스트(mock testing)와 유사한 기술을 사용할 수 있습니다(예에서는 MvcUriComponentsBuilder.on의 static import를 가정합니다).

UriComponents uriComponents = MvcUriComponentsBuilder
	.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

[Note]
컨트롤러 메서드 서명은 fromMethodCall을 통한 링크 생성에 사용할 수 있다고 가정할 때 디자인이 제한됩니다. 적절한 매개변수 서명이 필요한 것 외에도 반환 유형(즉, 링크 빌더 호출을 위한 런타임 프록시 생성)에는 기술적인 제한이 있으므로 반환 유형은 final이 아니어야 합니다. 특히, 뷰 이름에 대한 일반적인 String 반환 유형은 여기서 작동하지 않습니다. 대신 ModelAndView 또는 일반 Object(String 반환 값 포함)를 사용해야 합니다.

이전 예제에서는 MvcUriComponentsBuilder의 정적 메서드를 사용합니다. 내부적으로는 ServletUriComponentsBuilder를 사용하여 현재 요청의 구성표, 호스트, 포트, 컨텍스트 경로 및 서블릿 경로에서 기본 URL을 준비합니다. 이는 대부분의 경우에 잘 작동합니다. 그러나 때로는 충분하지 않을 수도 있습니다. 예를 들어 요청 컨텍스트 외부에 있거나(예: 링크를 준비하는 일괄 프로세스) 경로 접두사(예: 요청 경로에서 제거되어 다시 수정해야 하는 로케일 접두사)를 삽입해야 할 수 있습니다. 링크에 삽입했습니다).

이러한 경우 기본 URL을 사용하기 위해 UriComponentsBuilder를 허용하는 정적 fromXxx 오버로드된 메서드를 사용할 수 있습니다. 또는 기본 URL을 사용하여 MvcUriComponentsBuilder의 인스턴스를 만든 다음 인스턴스 기반 withXxx 메서드를 사용할 수 있습니다. 예를 들어 다음 목록에서는 withMethodCall을 사용합니다.

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

5.1부터 MvcUriComponentsBuilder는 클라이언트 시작 주소를 수신하는 ForwardedX-Forwarded-* 헤더의 정보를 무시합니다. 이러한 헤더를 추출하여 사용하거나 삭제하려면 ForwardedHeaderFilter를 사용하는 것이 좋습니다.

Links in Views

Thymeleaf, FreeMarker 또는 JSP와 같은 용도로 각 요청 매핑에 대해 해당하거나 고유하게 이름을 참조하여 추출된 컨트롤러에 대한 링크를 구축할 수 있습니다. 다음 예를 고려하시기 바랍니다.

@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

	@RequestMapping("/{country}")
	public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}

이전 컨트롤러가 주어지면 다음과 같이 JSP에서 링크를 준비할 수 있습니다.

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

앞의 예제는 Spring 태그 라이브러리(즉, META-INF/spring.tld)에 선언된 mvcUrl 함수에 의존하지만 자신만의 함수를 정의하거나 다른 템플릿 기술을 위해 유사한 함수를 준비하는 것이 쉽습니다.

작동 방식은 다음과 같습니다. 시작 시 모든 @RequestMapping에는 HandlerMethodMappingNamingStrategy를 통해 기본 이름이 할당됩니다. 기본 구현에서는 클래스의 대문자와 메서드 이름을 사용합니다(예를 들어 ThingControllergetThing 메서드는 "TC#getThing"이 됩니다). 이름 충돌이 있는 경우 @RequestMapping(name="..")을 사용하여 명시적인 이름을 할당하거나 자체 HandlerMethodMappingNamingStrategy를 구현할 수 있습니다.

0개의 댓글