过滤从 http 出站网关获得 200 状态的消息

filter messages that get 200 status from http outbound gateway

我有一个消息流,如果 GET HTTP 调用(通过 http:outbound-网关)的结果是 statusCode 200,我想 filter/drop 消息 - 即案例已经存在。换句话说,如果调用收到 404(未找到),流程应该继续。理想情况下,任何其他状态代码或异常都应该转到 errorHandler(就像现在一样)

我试过调用一个网关,该网关使用带有请求处理程序建议链的 http:outbound-gateway 链,我想我可以捕获 404,然后让事情继续,就好像它是不是错误,然后在 'filter' 中测试 statusCode 404。但是遇到了一些问题

  1. onFailureExpression 表达式未检测到 statusCode 404,日志显示 HttpClientErrorException,因此可能未捕获到异常?
  2. 我应该 return 从 onFailureExpression 得到什么值? null 或 payload 或 #payload ?理想情况下,我希望 HTTP 调用之前的有效负载保持原样。
  3. 我需要 trapException true 吗?
  4. 惊讶没有更简单的方法吗?耻辱不能只在 http:outbound-gateway httpStatusCodes 上声明被允许并被视为正常。我不认为 errorChannel 和错误处理程序是正确的方法,因为如果出现 404 状态代码,我希望原始流程继续。因此,将需要的常规行为放入错误流中看起来很奇怪,并且已经有了更高级别的错误处理程序。
2020-11-24 10:46:45,060 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:9095/ccacase/V1/case/challenge/CHG123456789" resulted in 404 (Not Found); invoking error handler
2020-11-24 10:46:45,134 [main] DEBUG org.springframework.integration.channel.DirectChannel - postSend (sent=true) on channel 'getChallengeCaseChannel', message: GenericMessage [payload=uk.gov.voa.integration.ccacasecheck.json.CdbEvent@624b3544[id=22,eventType=MIGRATE_CHALLENGE_CASE,asstRef=<null>,ccaCaseRef=CHG123456789,assessmentStatus=<null>,settlementCode=<null>,eventDateTime=2020-11-23T12:05,uarn=<null>,sourceActivityId=<null>,activityAction=<null>,additionalProperties={}], headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@54f6b629, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@54f6b629, originalPayload=uk.gov.voa.integration.ccacasecheck.json.CdbEvent@624b3544[id=22,eventType=MIGRATE_CHALLENGE_CASE,asstRef=<null>,ccaCaseRef=CHG123456789,assessmentStatus=<null>,settlementCode=<null>,eventDateTime=2020-11-23T12:05,uarn=<null>,sourceActivityId=<null>,activityAction=<null>,additionalProperties={}], message_id=22, history=cdbEventsAMQPChannel,routeEventChain,migrateChallengeCaseEventChannel,migrateChallengeCaseEventChain,statusFlow,getChallengeCaseChannel, id=6d99e890-fc56-74a8-1187-fbc3457685c8, timestamp=1606214804576}]
2020-11-24 10:46:45,135 [main] DEBUG org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor - SpEL Expression evaluation failed with Exception.org.springframework.web.client.HttpClientErrorException: 404 Not Found


Caused by: org.springframework.messaging.MessagingException: failed to transform message headers; nested exception is org.springframework.messaging.MessageHandlingException: Expression evaluation failed: @statusFlow.exchange(#root).headers[http_statusCode]; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
    at org.springframework.integration.transformer.HeaderEnricher.transform(HeaderEnricher.java:128)
    at org.springframework.integration.transformer.MessageTransformingHandler.handleRequestMessage(MessageTransformingHandler.java:89)
    ... 72 more
Caused by: org.springframework.messaging.MessageHandlingException: Expression evaluation failed: @statusFlow.exchange(#root).headers[http_statusCode]; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
    at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:143)
    at org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor.processMessage(ExpressionEvaluatingMessageProcessor.java:72)
    at org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor.processMessage(ExpressionEvaluatingHeaderValueMessageProcessor.java:71)
    at org.springframework.integration.transformer.HeaderEnricher.transform(HeaderEnricher.java:119)
    ... 73 more
Caused by: org.springframework.web.client.HttpClientErrorException: 404 Not Found
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
<int:chain id="migrateChallengeCaseEventChain" input-channel="migrateChallengeCaseEventChannel">

        <int:header-enricher>                                
            <int:header name="caseStatusCode" expression="@statusFlow.exchange(#root).headers[http_statusCode]" />    
        </int:header-enricher>
        
        <!-- to check http_statusCode header and drop any message with found 200 statusCode -->
        <int:filter ref="status200Filter"/>  
        
        <int:transformer ref="migrateChallengeCaseTransformer" />
        <int:transformer ref="jsonValidationTransformer" />
        <int:object-to-json-transformer object-mapper="springJacksonObjectMapper" />
        <int:header-enricher>
             <int:header name="contentType" value="application/json;charset=UTF-8" overwrite="true"/>
        </int:header-enricher>  
        <int-amqp:outbound-channel-adapter 
            amqp-template="amqpTemplate" exchange-name="cdbEvents.exchange" 
            routing-key="migrateCdbCcaChallengeCase.request.queue.binding" />
    </int:chain>    
    
    <int:gateway id="statusFlow" default-request-channel="getChallengeCaseChannel" />  
    
    <int:channel id="getChallengeCaseChannel" />
    
    <!-- Call API to see if Case already exists, 200 status code we want to filter/drop message   -->
    <int:chain id="getChallengeCaseChain" input-channel="getChallengeCaseChannel">
        <int-http:outbound-gateway id="httpOutboundGatewayChallengeCaseGet"     
                            expected-response-type="java.lang.String"                                           
                            http-method="GET"   charset="UTF-8"
                            extract-request-payload="true"  
                            request-factory="httpRequestFactory"                    
                            url="${mule.case.data.service.uri}/${case.challenge.subpath}/{ccaCaseRefValue}">
            <int-http:uri-variable name="ccaCaseRefValue" expression="headers['originalPayload'].ccaCaseRef"/>  
            <int-http:request-handler-advice-chain>
                    <bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
                        <!-- If true, the result of evaluating the onFailureExpression will be returned as the result -->
                        <property name="returnFailureExpressionResult" value="true" />
                        <property name="onSuccessExpression" value="payload" />
                        <!-- If true, any exception will be caught and null returned. Default false -->
                         <property name="trapException" value="true" /> 
                        <!-- Set the expression to evaluate against the root message after a failed handler invocation. The exception is available as the variable #exception. Defaults to payload, if failureChannel is configured. -->
                        <property name="onFailureExpression"  value="#exception.cause.statusCode == 404 ? payload : #exception"/> 
                        <!-- Set the channel name to which to send the ErrorMessage after evaluating the failure expression. -->
                        <!-- <property name="failureChannel" ref="#headers['replyChannel']" />  -->
                    </bean>
                </int-http:request-handler-advice-chain>
        </int-http:outbound-gateway>
    
    </int:chain>    

使用的解决方案是实现自定义 ErrorHandler,它扩展默认值并覆盖 hasError

/**
 * To be used with an 'int-http:outbound-gateway' to make a HTTP call and allow a statusCode 404 
 * response to be treated as normal (as well as a 200 statusCode). 
 * Normally a statusCode 404 would throw a 
 * org.springframework.web.client.HttpClientErrorException: 404 Not Found
 * and expect the caller to deal with an error-channel.  
 * This allows processing to continue and subsequently test for a 404 response
 * and route differently 
 *
 */
public class Allow404StatusCodeResponseHandler extends DefaultResponseErrorHandler  {

    /**
     * OVERRIDEN 
     * 
     * Indicate whether the given response has any errors.
     * <p>Implementations will typically inspect the
     * {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response.
     * @param response the response to inspect
     * @return {@code true} if the response indicates an error; {@code false} otherwise
     * @throws IOException in case of I/O errors
     */
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        int rawStatusCode = response.getRawStatusCode();
        return (rawStatusCode == 404 || rawStatusCode == 200) ? false : true;  // 404 to be treated as normal and testable downstream
    }

}

然后在 int-http:outbound-gateway

中设置

<int-http:outbound-gateway> 完全基于来自 Spring Web 的 RestTemplate。 那个带有 DefaultResponseErrorHandler,它确实将 4xx 视为错误 - 标准 HTTP 协议行为:https://www.restapitutorial.com/httpstatuscodes.html#.

另请参阅 class JavaDocs:

/**
 * Spring's default implementation of the {@link ResponseErrorHandler} interface.
 *
 * <p>This error handler checks for the status code on the
 * {@link ClientHttpResponse}. Any code in the 4xx or 5xx series is considered
 * to be an error. This behavior can be changed by overriding
 * {@link #hasError(HttpStatus)}. Unknown status codes will be ignored by
 * {@link #hasError(ClientHttpResponse)}.
 *
 * <p>See {@link #handleError(ClientHttpResponse)} for more details on specific
 * exception types.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @since 3.0
 * @see RestTemplate#setErrorHandler
 */
public class DefaultResponseErrorHandler implements ResponseErrorHandler {

是的,您可以将任何自定义 ResponseErrorHandler 设置到 <int-http:outbound-gateway> 中以抑制默认行为,但我仍然认为最好处理 ExpressionEvaluatingRequestHandlerAdvice 来处理 HttpClientErrorException 及其 getStatusCode()。是的,您可能需要将 trapException 设置为 true 以避免 HttpClientErrorException 的 re-throw。是的,在失败流程中,您可以在请求之前访问原始消息。参见 ErrorMessageMessagingException 作为其 payloadfailedMessage 属性。

您可能会尝试使用更简单的您的 use-case 特定逻辑来实现您自己的 AbstractRequestHandlerAdvice:忽略 200 并适当地处理 404https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#message-handler-advice-chain

状态代码 200 将出现在回复 AbstractIntegrationMessageBuilderHttpHeaders.STATUS_CODE header 中,供您在建议中考虑。