Java 在春季使用ClientHttpRequestInterceptor时如何中止请求?
如果阅读onJava 在春季使用ClientHttpRequestInterceptor时如何中止请求?,java,spring,rest,Java,Spring,Rest,如果阅读onclienthttpprequestinterceptor.intercept方法,它会显示: 使用clienthttpprequestexecution.execute(org.springframework.http.HttpRequest,byte[])执行请求,或者不执行请求以完全阻止执行 此语句的第二部分是我试图实现的(基于某些标准完全阻止执行)。然而,我并没有成功地做到这一点。我已经做了几个小时的研究,但仍然找不到任何关于如何做到这一点的例子 我尝试返回null,后来在S
clienthttpprequestinterceptor.intercept
方法,它会显示:
使用clienthttpprequestexecution.execute(org.springframework.http.HttpRequest,byte[])执行请求,或者不执行请求以完全阻止执行
此语句的第二部分是我试图实现的(基于某些标准完全阻止执行)。然而,我并没有成功地做到这一点。我已经做了几个小时的研究,但仍然找不到任何关于如何做到这一点的例子
我尝试返回null,后来在Spring试图从响应中获取statusCode时,我在流中得到了NPE(下面的异常)
编辑:以下是一个简单的拦截器,供我参考,以了解我试图实现的目标:
public class FailSafeInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try{
return execution.execute(request, body);
}catch(Throwable e){
return null;
}
}
}
可能的解决方案:在对问题熟睡之后,我设法使用以下代码(使用MockResponse)使其工作。但我不确定这是否是doc使用方法的意思。如果有更好的方法,我会感谢你的帮助
public class FailSafeInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try{
return execution.execute(request, body);
}catch(Throwable e){
return new MockClientHttpResponse(new byte[]{}, HttpStatus.OK);
}
}
}
MockClientHttpResponse
来自spring test
,在生产代码中包含一个测试类很难
如果要在检测到某个内容后中止发送整个请求,只需抛出RuntimeException
(或使用内置的RestClientException
):
@覆盖
公共ClientHttpResponse截获(HttpRequest请求,字节[]正文,ClientHttpPrequesteExecution执行)引发IOException{
如果(checkNeedAbortSendingRequst()){
抛出新的RestClientException(“blablablablabla”);
}
返回执行。执行(请求,正文);
}
更新:
如果您想实现故障安全,您当前的解决方案看起来不错,希望在捕获异常(可能是500?)时使用非200状态代码。由于我也找不到一个
ClientHttpResponse
实现,它可以从头开始创建,或者在spring mvc
中没有任何外部依赖项,所以我会将MockClientHttpResponse
从spring test
复制到要使用的项目中。
在我的例子中,我不想在我们的开发环境中向外部服务发送请求。我们使用restemplate
与外部服务进行交互(实际上,我们使用的是配置了restemplate
)的。但是,我希望在环境之间尽可能少地更改配置
因此,我提出的解决方案是使用一个配置文件范围的bean,这样当选择dev
配置文件时,它将用一个从未实际执行请求的bean替换用于外部通信的restemplate
bean
以下是我的配置:
package com.example.myproject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Configuration
public class InfrastructureConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MyApplication.class);
/**
* This configuration is used whenever we are running in an environment that
* does not have access to external endpoints.
**/
@Configuration
@Profile("sit")
public static class FakeExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
// Returns a Rest Template which will always return HttpStatus.NO_CONTENT without actually executing the request.
return new RestTemplateBuilder()
.additionalInterceptors(((request, body, execution) -> {
LOGGER.info("Fake external request sent. Method: {} URI: {} Body: {} Headers: {}", request.getMethodValue(), request.getURI(), new String(body), request.getHeaders().toString());
return new AbstractClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream("" .getBytes(StandardCharsets.UTF_8));
}
@Override
public int getRawStatusCode() {
return HttpStatus.NO_CONTENT.value();
}
@Override
public String getStatusText() {
return HttpStatus.NO_CONTENT.getReasonPhrase();
}
@Override
public void close() {
}
};
}))
.build();
}
}
/**
* This configuration is used whenever we are running in an environment that
* does have access to external endpoints.
**/
@Configuration
@Profile({"test", "prod"})
public static class ExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
return new RestTemplateBuilder()
.build();
}
}
}
我选择将
org.springframework.http.client.AbstractClientHttpResponse
实现为一个内嵌匿名类,因为我希望这是唯一可以使用它的实例,并且没有那么多需要重写的方法。我选择始终返回HTTP状态204无内容,因为这似乎对我最有意义,但可以选择任何其他状态代码。(我很想使用I________茶壶
,但它是一个4xx状态,被认为是一个错误:()你能展示代码吗?我同意你的观点,在产品代码上有测试类是很尴尬的。我的目的是要有一个故障保护拦截器,它只记录错误并继续运行,而不会影响应用程序流的其余部分。抛出runtimeException与此相反。我也不想在所有客户端代码上都有catch块。Also、 如果这就是doc的意思,那就有点愚蠢了。因为抛出未经检查的异常当然会停止方法其余部分的执行。我不认为这就是javadoc所说的“或者不执行请求以完全阻止执行”的意思因为这基本上适用于Java中的任何方法。因此,您在注释中提到的意图与您在问题中提到的要中止请求的意图完全不同。此外,您不需要在所有客户端代码中显式捕获RuntimeException
。是的,如果您想要“故障保护”,您当前的解决方案看起来不错,但是如果捕获到异常,我不会使用“200”作为状态代码(可能是使用500?),并且将手动创建ClientHttpResponse
,而不是使用MockClientHttpResponse
。(关于这个问题的所有优点都不清楚我的意图。我将在今晚晚些时候进行编辑)关于状态代码的观点很好。在创建ClientHttpResponse
时,我没有幸做到这一点。我发现的该接口的所有实现,要么没有公共构造函数,要么如果有,除了MockClientHttpResponse
之外,似乎不是很简单。如果您有解决方案,请编辑您的回答该代码,我会将其标记为答案。啊,我也找不到允许轻松创建假的ClientHttpResponse
的实现,因此我的建议是将mockclienttpresponse
从spring测试复制到项目代码库并使用它
package com.example.myproject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Configuration
public class InfrastructureConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MyApplication.class);
/**
* This configuration is used whenever we are running in an environment that
* does not have access to external endpoints.
**/
@Configuration
@Profile("sit")
public static class FakeExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
// Returns a Rest Template which will always return HttpStatus.NO_CONTENT without actually executing the request.
return new RestTemplateBuilder()
.additionalInterceptors(((request, body, execution) -> {
LOGGER.info("Fake external request sent. Method: {} URI: {} Body: {} Headers: {}", request.getMethodValue(), request.getURI(), new String(body), request.getHeaders().toString());
return new AbstractClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
return new HttpHeaders();
}
@Override
public InputStream getBody() {
return new ByteArrayInputStream("" .getBytes(StandardCharsets.UTF_8));
}
@Override
public int getRawStatusCode() {
return HttpStatus.NO_CONTENT.value();
}
@Override
public String getStatusText() {
return HttpStatus.NO_CONTENT.getReasonPhrase();
}
@Override
public void close() {
}
};
}))
.build();
}
}
/**
* This configuration is used whenever we are running in an environment that
* does have access to external endpoints.
**/
@Configuration
@Profile({"test", "prod"})
public static class ExternalConfig {
@Bean
@Qualifier("externalRestTemplate")
RestTemplate externalRestTemplate() {
return new RestTemplateBuilder()
.build();
}
}
}