Argument Resolver에 대한 이해
Argument Resolver란?
Body에 담아 들어오거나 @PathVariable를 이용하는 데이터는 Controller에서 바로 받을 수 있지만 세션, 쿠키, 헤더 등에서 제공하는 데이터들은 파라미터로 받는 경우 Argument Resolver를 활용해서 바인딩할 수 있다.
개인적으로 세션, 쿠키, 헤더 말고 활용하면 안되는것인지, 코드 가독성이 떨어지는것인지 의문점이 생긴다. 🤔 (참 좋은 방법인거 같은데…)
ClientIp Annotation 생성
Custom Annotation을 생성하였다. 이게 왜 필요하냐면 파라미터 앞에 붙여줘서 Resolver에서 동작여부를 판단하게 된다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClientIp {
}
UserInfo 클래스 생성
Http Response Object라고 생각하면 된다.
@Getter
@Setter
@Builder
@@AllArgsConstructor
public class UserInfo {
private String name;
private int age;
private String ip;
}
ClientIpArgumentResolver 클래스 생성
이 코드는 복잡해보이나 대강 보면
supportsParameter
: @ClientIp 어노테이션이 포함되었는지 동작여부를 결정한다.
resolveArgument
: Controller로 향하는 request를 전달받아서 파라미터에 바인딩하게 된다. 원하는 형태의 Object를 설계해서 리턴할 수 있다. 미리 정의한 UserInfo를 반환한다.
getClientIp
: 당연한 거지만 Client 에서 request한것은 로드밸런싱되서 WAS에 전달되기 때문에 ClientIp는 내 아이피가 아니라 로드밸런서의 아이피가 될 것이다. 그래서 헤더에서 벤더마다 다른 Key값을 가져와야 하는데 getClientIp 메서드에서 사용된 key값을 사용한다.
이글
@Component
public class ClientIpArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
if (methodParameter.hasParameterAnnotation(ClientIp.class)) {
return true;
}
return false;
}
@Override
public UserInfo resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest)nativeWebRequest.getNativeRequest();
return UserInfo.builder()
.name(request.getParameter("name"))
.age(Integer.parseInt(request.getParameter("age")))
.ip(getClientIp(request))
.build();
}
private String getClientIp(HttpServletRequest request) {
return Stream.of(
request.getHeader("X-Forwarded-For"),
request.getHeader("Proxy-Client-IP"),
request.getHeader("WL-Proxy-Client-IP"),
request.getHeader("HTTP_CLIENT_IP"),
request.getHeader("HTTP_X_FORWARDED_FOR")
)
.filter(Objects::nonNull)
.findFirst()
.orElse(request.getRemoteAddr());
}
}
ClientIpArgumenrResolver를 WebMvcConfig 클래스에 등록
Argument Resolver로 ClientIpArgumentResolver를 등록한다.
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final ClientIpArgumentResolver clientIpArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(clientIpArgumentResolver);
}
}
ClientIpArgumentResolver 테스트용 API 생성
마지막 API를 테스트해본다.
@RestController
public class TestApiController {
@GetMapping("/api/ip")
public UserInfo getIp(@ClientIp UserInfo userInfo) {
return userInfo;
}
}
Result
IP의 경우 IPv4의 형태는 jvm 옵션에 -Djava.net.preferIPv4Stack=true 사용해야 한다. (현재는 null)