在Spring中使用自定义解析器返回HTTP状态BAD_REQUEST

f1tvaqid  于 2023-11-17  发布在  Spring
关注(0)|答案(4)|浏览(135)

我正在使用Sping Boot 1.5.15开发一个REST API,我实现了一个客户HandlerMethodArgumentResolver来Map一个HTTP header,具体来说,我将HTTP header的值赋给Some-Header,去掉了前缀“XXX“。
首先,我定义了一个自定义注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface SomeHeader {
}

字符串
然后,我实现了一个自定义解析器。

public class SomeHeaderArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterAnnotation(SomeHeader.class) != null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        final String headerValue = request.getHeader("Some-Header");

        return headerValue.replace("XXX ", "");
    }
}


最后,我让Spring知道了配置类中的解析器。

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new SomeHeaderArgumentResolver());
    }
}


现在,我可以在任何需要的控制器中使用以下Map。

@PostMapping("/some/paath")
public void someMethod(@SomeHeader String someHeaderValue) {
   // Method body...
}


然而,Some-Header信息对我来说是强制性的。我希望如果它不存在,Spring向调用者返回400 Bad Request响应。这与我可以使用@RequestHeader("Some-Header") annotation获得的行为相同。
我可以复制同样的行为吗?可能,我不想使用专用的 * 控制器建议 *。

ilmyapht

ilmyapht1#

如果您看到用于@RequestHeaderRequestHeaderMethodArgumentResolver的实现,您将看到AbstractNamedValueMethodArgumentResolver抽象类的handleMissingValue方法的重写实现,如下所示:

@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
        throw new ServletRequestBindingException("Missing request header '" + name +
                "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}

字符串
这个handleMissingValue方法在AbstractNamedValueMethodArgumentResolverresolveArgument方法中使用,RequestHeaderMethodArgumentResolver基于某些条件扩展了AbstractNamedValueMethodArgumentResolver。所以当header不存在并且抛出ServletRequestBindingException时,Spring的DefaultHandlerExceptionResolver将处理它并发送400响应。
所以这就是@RequestHeader的验证工作方式。所以你可以在SomeHeaderArgumentResolver类的resolveArgument方法中实现类似的东西,如下所示:

public class SomeHeaderArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterAnnotation(SomeHeader.class) != null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        final String headerValue = request.getHeader("Some-Header");
        if(headerValue != null){
           return headerValue.replace("XXX ", "");
        } else {
           //handle scenario if header absent with ServletRequestBindingException
        }
       }
    }

qfe3c7zg

qfe3c7zg2#

您可以为任何情况声明您自己的异常,并在控制器中设置ExceptionException以返回正确的http-status。

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({SameHeaderException.class})
public Object onSameHeaderException(SameHeaderException e) {
    return ImmutableMap.of("reason", e.getMessage());
}

字符串
所以如果header不存在,你可以抛出这个异常:

if (someHeaderValue == null) { throw new SameHeaderException(); }

fnatzsnv

fnatzsnv3#

感谢@madhu-bhat的建议,我明白了哪个类是正确的扩展,让Spring发挥魔力。
Spring使用类RequestHeaderMethodArgumentResolver来解析Java对象内部的HTTP header值,它扩展了抽象类AbstractNamedValueMethodArgumentResolver。这个类允许您使用createNamedValueInfo方法指定header值是否具有某些默认值。
它遵循代码。

public class SomeHeaderArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        // The second parameter specifies if the value is required, 
        // and the third if there is some default value.
        return new NamedValueInfo("", true, null);
    }

    @Override
    protected Object resolveName(String name, 
                                 MethodParameter parameter, 
                                 NativeWebRequest request) {
        final String headerValue = request.getHeader("Some-Value");
        if (StringUtils.isEmpty(headerValue)) {
            // Returning null tells Spring that there is no value for the parameter
            return null;
        }
        return headerValue.replace("XXX ", "");
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(SomeHeader.class) &&
                !Map.class.isAssignableFrom(
                    parameter.nestedIfOptional().getNestedParameterType()));
    }
}

字符串
我唯一不喜欢的是,我正在使用开发的结构来处理 * 命名值 *,但我没有一个 * 命名值 *。
希望对你有帮助。

nuypyhwy

nuypyhwy4#

我基于这里的想法创建了一个Sping Boot 2.7.17示例。该示例使用了一个**@RequestHeaderEmail**自定义参数annotation。当此annotation出现时,它会将“X-EMAIL”请求头的值放入其中。如果没有这样的头或者它是null或空的,则REST方法将返回Bad Request。
使用方法:

@RestController
public class RqController {
   @GetMapping
   String displayEmailHeader(@RequestHeaderEmail String email) {
     return email;
   }
}

字符串
注解:

@Target(ElementType.PARAMETER)
   @Retention(RetentionPolicy.RUNTIME)
   @Documented
   public @interface RequestHeaderEmail {}


解析器:

public class RequestHeaderEmailResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return String.class.equals(parameter.getParameter().getType()) && parameter.hasParameterAnnotation(RequestHeaderEmail.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String email = webRequest.getHeader("X-EMAIL");
        if (email == null || email.isEmpty()) throw new ServletRequestBindingException("X-EMAIL header should not be null or empty");

        return email;
    }
}


设置:

@Configuration
@EnableWebMvc
public class AnnotationConfiguration implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new RequestHeaderEmailResolver());
    }

}


下面是一个带有单元测试的github repo:https://github.com/peterborkuti/request_param_custom_annotation/tree/main

相关问题