Spring RestTemplate 调用带有错误的 web 服务并分析状态码

Posted

技术标签:

【中文标题】Spring RestTemplate 调用带有错误的 web 服务并分析状态码【英文标题】:Spring RestTemplate invoking webservice with errors and analyze status code 【发布时间】:2013-03-02 12:38:13 【问题描述】:

我设计了一个webservice,如果请求参数OK就执行一个任务,或者如果请求参数错误或者为空返回401 Unauthorized HTTP状态码。

我正在使用RestTemplate 执行测试,如果网络服务回复成功,我可以验证 HTTP 200 OK 状态。但是,我无法测试 HTTP 401 错误,因为 RestTemplate 本身会引发异常。

我的测试方法是

@Test
public void testUnauthorized()

    Map<String, Object> params = new HashMap<String, Object>();
    ResponseEntity response = restTemplate.postForEntity(url, params, Map.class);
    Assert.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    Assert.assertNotNull(response.getBody());

异常日志是

org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:88)
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:533)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:489)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:447)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:318)

如何测试 web 服务是否回复 HTTP 状态码 401?

【问题讨论】:

我的回答对你有用吗? 【参考方案1】:

您可以使用弹簧测试。这更容易:

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:your-context.xml")
public class BasicControllerTest 

        @Autowired
        protected WebApplicationContext wac;
        protected MockMvc mockMvc;

        @Before
        public void setUp() throws Exception 
        mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        

        @Test
        public void testUnauthorized()

        mockMvc.perform(MockMvcRequestBuilders
                            .post("your_url")
                            .param("name", "values")
        .andDo(MockMvcResultHandlers.print())
        .andExpect(MockMvcResultMatchers.status().isUnauthorized()
        .andExpect(MockMvcResultMatchers.content().string(Matchers.notNullValue()));
        

【讨论】:

如果您需要向标头添加身份验证,则此方法不起作用,因为默认情况下 spring 不会通过过滤器运行测试。【参考方案2】:

您需要实现ResponseErrorHandler,以便在使用rest模板从服务中获取非2xx响应代码时拦截响应代码、正文和标头。复制您需要的所有信息,将其附加到您的自定义异常并抛出它,以便您可以在测试中捕获它。

public class CustomResponseErrorHandler implements ResponseErrorHandler 

    private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();

    public boolean hasError(ClientHttpResponse response) throws IOException 
        return errorHandler.hasError(response);
    

    public void handleError(ClientHttpResponse response) throws IOException 
        String theString = IOUtils.toString(response.getBody());
        CustomException exception = new CustomException();
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("code", response.getStatusCode().toString());
        properties.put("body", theString);
        properties.put("header", response.getHeaders());
        exception.setProperties(properties);
        throw exception;
    

现在你需要在测试中做的是,在 RestTemplate 中设置这个 ResponseErrorHandler ,

RestTemplate restclient = new RestTemplate();
restclient.setErrorHandler(new CustomResponseErrorHandler());
try 
    POJO pojo = restclient.getForObject(url, POJO.class); 
 catch (CustomException e) 
    Assert.isTrue(e.getProperties().get("body")
                    .equals("bad response"));
    Assert.isTrue(e.getProperties().get("code").equals("400"));
    Assert.isTrue(((HttpHeaders) e.getProperties().get("header"))
                    .get("fancyheader").toString().equals("[nilesh]"));

【讨论】:

请注意,如果您遵循此模式,CustomException 是未经检查的异常。例如,您可以扩展 RuntimeException。否则,异常无法通过该方法的“抛出 IOException”。 注意,我使用的是 post 请求 @snowe2010 您可以执行以下操作来获取 HttpCustomException。 ------> catch (Exception e) if(e.getCause() instanceof HttpCustomException) HttpCustomException customException= (HttpCustomException)e.getCause(); l.warn("Rest 模板的自定义异常"); @snowe2010 这是编译错误吗?还是运行时错误?请记住,您正在扩展一个已检查的异常,例如 IOException。调用者必须小心处理。你总是可以扩展 RuntimeException 但我不知道你的用例 @nilesh 这是一个编译错误。我最终选择了一条不同的路线,没有使用 RestTemplate,因为我知道如何正确使用 MockMvc。【参考方案3】:

作为 nilesh 提出的解决方案的替代方案,您还可以使用 spring 类 DefaultResponseErrorHandler。您还需要覆盖它的 hasError(HttpStatus) 方法,这样它就不会在不成功的结果上抛出异常。

restTemplate.setErrorHandler(new DefaultResponseErrorHandler()
    protected boolean hasError(HttpStatus statusCode) 
        return false;
    );

【讨论】:

这对我来说非常有效。因为我使用的 API 返回 JSON 错误,所以抛出的异常禁止我查看。这给了我足够的调试灵活性。【参考方案4】:

在我的休息服务中,我捕获 HttpStatusCodeException 而不是 Exception 因为HttpStatusCodeException有获取状态码的方法

catch(HttpStatusCodeException e) 
    log.debug("Status Code", e.getStatusCode());

【讨论】:

对于一个快速、快速的解决方案,这很好用,如果您需要访问响应内容,您可以捕获 HttpClientErrorException 和/或 HttpServerErrorException,两者都有 getResponseBodyAsString() 方法 我的两分钱:如果你使用 TestRestTemplate 类(Spring Boot),你应该使用 catch(RestClientException)。【参考方案5】:

从 Spring 4.3 开始,有一个 RestClientResponseException 包含实际的 HTTP 响应数据,例如状态代码、响应正文和标头。你可以抓住它。

RestClientResponseException Java Doc

【讨论】:

以上是关于Spring RestTemplate 调用带有错误的 web 服务并分析状态码的主要内容,如果未能解决你的问题,请参考以下文章

带有 Spring RESTTemplate 的泛型

如何使用带有Json主体和标头的RestTemplate进行POST调用? [重复]

Spring Cloud RestTemplate学习

※Spring全家桶从入门到X神-微服务+远程调用(RestTemplate)

Spring Boot:如何使用 WebClient 而不是 RestTemplate 来执行非阻塞和异步调用

Spring RestTemplate 配置策略从单个 API 调用多个休息服务