This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git


The following commit(s) were added to refs/heads/master by this push:
     new 018a12f30 [#4303]compatible to old systems allowing different response 
type in exception processing(#4307)
018a12f30 is described below

commit 018a12f30f73556c38a16d1352e0e3bac3932341
Author: liubao68 <[email protected]>
AuthorDate: Mon May 6 10:39:45 2024 +0800

    [#4303]compatible to old systems allowing different response type in 
exception processing(#4307)
---
 .../common/rest/RestProducerInvocationFlow.java    |  4 +-
 .../rest/filter/inner/RestServerCodecFilter.java   | 11 +++-
 .../core/exception/DefaultExceptionProcessor.java  |  3 +-
 .../apache/servicecomb/core/filter/FilterNode.java | 11 +---
 .../core/invocation/ProducerInvocationFlow.java    | 72 ++++++++--------------
 .../demo/springmvc/SpringmvcClient.java            |  2 +-
 .../demo/springmvc/client/ResponseOKData.java      | 48 +++++++++++++++
 .../demo/springmvc/client/TestDownloadSchema.java  | 47 ++++++++++++++
 .../demo/springmvc/server/DownloadSchema.java      | 41 +++++++++---
 .../demo/springmvc/server/ResponseOKData.java      | 44 +++++++++++++
 .../demo/springmvc/server/ResponseOKException.java | 20 ++++++
 .../server/ResponseOKExceptionConverter.java       | 40 ++++++++++++
 ...e.servicecomb.core.exception.ExceptionConverter | 18 ++++++
 .../highway/HighwayServerCodecFilter.java          | 12 +++-
 .../highway/HighwayServerCodecFilterTest.java      |  3 +-
 15 files changed, 305 insertions(+), 71 deletions(-)

diff --git 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationFlow.java
 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationFlow.java
index c8e32471f..b09406bd4 100644
--- 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationFlow.java
+++ 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/RestProducerInvocationFlow.java
@@ -29,6 +29,7 @@ import 
org.apache.servicecomb.foundation.common.utils.PartUtils;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
 import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,7 +61,8 @@ public class RestProducerInvocationFlow extends 
ProducerInvocationFlow {
   @Override
   protected void sendResponse(Invocation invocation, Response response) {
     invocation.getInvocationStageTrace().startProviderSendResponse();
-    if (isDownloadFileResponseType(invocation, response)) {
+    boolean failed = response.getResult() instanceof InvocationException;
+    if (!failed && isDownloadFileResponseType(invocation, response)) {
       responseEx.sendPart(PartUtils.getSinglePart(null, response.getResult()))
           .whenComplete((r, e) -> flushResponse(invocation));
       return;
diff --git 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
index d727a7141..b1b3c37b7 100644
--- 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
+++ 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
@@ -46,6 +46,7 @@ import 
org.apache.servicecomb.foundation.vertx.stream.BufferOutputStream;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.TransportContext;
 import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -124,13 +125,19 @@ public class RestServerCodecFilter extends AbstractFilter 
implements ProviderFil
     responseEx.setStatus(response.getStatusCode());
     copyHeadersToHttpResponse(response.getHeaders(), responseEx);
 
-    if (download) {
+    boolean failed = response.getResult() instanceof InvocationException;
+
+    if (!failed && download) {
       return CompletableFuture.completedFuture(response);
     }
 
     responseEx.setContentType(produceProcessor.getName());
     try (BufferOutputStream output = new 
BufferOutputStream(Unpooled.compositeBuffer())) {
-      produceProcessor.encodeResponse(output, response.getResult());
+      if (failed) {
+        produceProcessor.encodeResponse(output, ((InvocationException) 
response.getResult()).getErrorData());
+      } else {
+        produceProcessor.encodeResponse(output, response.getResult());
+      }
 
       responseEx.setBodyBuffer(output.getBuffer());
 
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/exception/DefaultExceptionProcessor.java
 
b/core/src/main/java/org/apache/servicecomb/core/exception/DefaultExceptionProcessor.java
index 2d9a0b14e..f0f101f91 100644
--- 
a/core/src/main/java/org/apache/servicecomb/core/exception/DefaultExceptionProcessor.java
+++ 
b/core/src/main/java/org/apache/servicecomb/core/exception/DefaultExceptionProcessor.java
@@ -191,8 +191,7 @@ public class DefaultExceptionProcessor implements 
ExceptionProcessor {
     if (invocation != null) {
       logProducerException(invocation, invocationException);
     }
-    return Response.status(invocationException.getStatus())
-        .entity(invocationException.getErrorData());
+    return Response.createFail(invocationException);
   }
 
   @Override
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/filter/FilterNode.java 
b/core/src/main/java/org/apache/servicecomb/core/filter/FilterNode.java
index b589730f7..44a5b34d9 100644
--- a/core/src/main/java/org/apache/servicecomb/core/filter/FilterNode.java
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/FilterNode.java
@@ -70,15 +70,6 @@ public class FilterNode {
 
     String stage = 
invocation.getInvocationStageTrace().recordStageBegin(this.filter.getNameWithOrder());
     return AsyncUtils.tryCatchSupplierFuture(() -> filter.onFilter(invocation, 
nextNode)
-            .whenComplete((r, e) -> 
invocation.getInvocationStageTrace().recordStageEnd(stage)))
-        .thenApply(this::rethrowExceptionInResponse);
-  }
-
-  private Response rethrowExceptionInResponse(Response response) {
-    if (response.isFailed() && response.getResult() instanceof Throwable) {
-      throw AsyncUtils.rethrow(response.getResult());
-    }
-
-    return response;
+        .whenComplete((r, e) -> 
invocation.getInvocationStageTrace().recordStageEnd(stage)));
   }
 }
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/invocation/ProducerInvocationFlow.java
 
b/core/src/main/java/org/apache/servicecomb/core/invocation/ProducerInvocationFlow.java
index 9ccef3b99..88371433c 100644
--- 
a/core/src/main/java/org/apache/servicecomb/core/invocation/ProducerInvocationFlow.java
+++ 
b/core/src/main/java/org/apache/servicecomb/core/invocation/ProducerInvocationFlow.java
@@ -19,14 +19,16 @@ package org.apache.servicecomb.core.invocation;
 import java.util.concurrent.CompletableFuture;
 
 import org.apache.servicecomb.core.Invocation;
-import org.apache.servicecomb.core.exception.Exceptions;
-import org.apache.servicecomb.foundation.common.utils.ExceptionUtils;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
 import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import jakarta.ws.rs.core.Response.Status;
+
 public abstract class ProducerInvocationFlow {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(ProducerInvocationFlow.class);
 
@@ -66,56 +68,36 @@ public abstract class ProducerInvocationFlow {
     if (invocation.isEdge()) {
       invocation.getMicroserviceMeta().getEdgeFilterChain()
           .onFilter(invocation)
-          .whenComplete((response, Throwable) -> sendResponse(invocation, 
response))
-          .whenComplete((response, Throwable) -> finishInvocation(invocation, 
response, Throwable));
+          .whenComplete((response, throwable) -> {
+            if (throwable != null) {
+              // Server codec operates on Response. So the filter chain result 
should be Response and
+              // will never throw exception.
+              LOGGER.error("Maybe a fatal bug that should be addressed.", 
throwable);
+              response = Response.createFail(new 
InvocationException(Status.INTERNAL_SERVER_ERROR,
+                  new CommonExceptionData("Internal error, check logs for 
details.")));
+            }
+            sendResponse(invocation, response);
+            finishInvocation(invocation, response);
+          });
       return;
     }
     invocation.getMicroserviceMeta().getProviderFilterChain()
         .onFilter(invocation)
-        .whenComplete((response, Throwable) -> sendResponse(invocation, 
response))
-        .whenComplete((response, Throwable) -> finishInvocation(invocation, 
response, Throwable));
+        .whenComplete((response, throwable) -> {
+          if (throwable != null) {
+            // Server codec operates on Response. So the filter chain result 
should be Response and
+            // will never throw exception.
+            LOGGER.error("Maybe a fatal bug that should be addressed.", 
throwable);
+            response = Response.createFail(new 
InvocationException(Status.INTERNAL_SERVER_ERROR,
+                new CommonExceptionData("Internal error, check logs for 
details.")));
+          }
+          sendResponse(invocation, response);
+          finishInvocation(invocation, response);
+        });
   }
 
-  private void finishInvocation(Invocation invocation, Response response, 
Throwable throwable) {
+  private void finishInvocation(Invocation invocation, Response response) {
     invocation.onFinish(response);
-
-    tryLogException(invocation, throwable);
-  }
-
-  private void tryLogException(Invocation invocation, Throwable throwable) {
-    if (throwable == null) {
-      return;
-    }
-
-    throwable = Exceptions.unwrap(throwable);
-    if (requestEx == null) {
-      logException(invocation, throwable);
-      return;
-    }
-
-    logException(invocation, requestEx, throwable);
-  }
-
-  private void logException(Invocation invocation, Throwable throwable) {
-    if (Exceptions.isPrintInvocationStackTrace()) {
-      LOGGER.error("Failed to finish invocation, operation:{}.", 
invocation.getMicroserviceQualifiedName(), throwable);
-      return;
-    }
-
-    LOGGER.error("Failed to finish invocation, operation:{}, message={}.", 
invocation.getMicroserviceQualifiedName(),
-        ExceptionUtils.getExceptionMessageWithoutTrace(throwable));
-  }
-
-  private void logException(Invocation invocation, HttpServletRequestEx 
requestEx, Throwable throwable) {
-    if (Exceptions.isPrintInvocationStackTrace()) {
-      LOGGER.error("Failed to finish invocation, operation:{}, request 
uri:{}.",
-          invocation.getMicroserviceQualifiedName(), 
requestEx.getRequestURI(), throwable);
-      return;
-    }
-
-    LOGGER.error("Failed to finish invocation, operation:{}, request uri:{}, 
message={}.",
-        invocation.getMicroserviceQualifiedName(), requestEx.getRequestURI(),
-        ExceptionUtils.getExceptionMessageWithoutTrace(throwable));
   }
 
   protected abstract Invocation sendCreateInvocationException(Throwable 
throwable);
diff --git 
a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java
 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java
index 291900ea4..849fd56a1 100644
--- 
a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java
+++ 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/SpringmvcClient.java
@@ -80,7 +80,7 @@ public class SpringmvcClient {
       LOGGER.error("-------------- test failed -------------");
     }
     TestMgr.summary();
-    LOGGER.info("-------------- last time updated checks(maybe more/less): 
1313 -------------");
+    LOGGER.info("-------------- last time updated checks(maybe more/less): 
1341 -------------");
   }
 
   private static void changeTransport(String microserviceName, String 
transport) {
diff --git 
a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java
 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java
new file mode 100644
index 000000000..df2bf9c5f
--- /dev/null
+++ 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/ResponseOKData.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.demo.springmvc.client;
+
+public class ResponseOKData {
+  private String errorCode;
+
+  private String errorMessage;
+
+  public ResponseOKData() {
+
+  }
+
+  public ResponseOKData(String errorCode, String errorMessage) {
+    this.errorCode = errorCode;
+    this.errorMessage = errorMessage;
+  }
+
+  public String getErrorCode() {
+    return errorCode;
+  }
+
+  public void setErrorCode(String errorCode) {
+    this.errorCode = errorCode;
+  }
+
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  public void setErrorMessage(String errorMessage) {
+    this.errorMessage = errorMessage;
+  }
+}
diff --git 
a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java
 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java
index 20a920a63..4e5fc967a 100644
--- 
a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java
+++ 
b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownloadSchema.java
@@ -17,25 +17,72 @@
 
 package org.apache.servicecomb.demo.springmvc.client;
 
+import java.net.URI;
 import java.util.Collections;
+import java.util.List;
 
 import org.apache.servicecomb.demo.CategorizedTestCase;
 import org.apache.servicecomb.demo.TestMgr;
 import org.apache.servicecomb.foundation.vertx.http.ReadStreamPart;
 import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder;
+import org.apache.servicecomb.registry.DiscoveryManager;
+import org.apache.servicecomb.registry.api.DiscoveryInstance;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
 
 @Component
 public class TestDownloadSchema implements CategorizedTestCase {
+  @Autowired
+  DiscoveryManager discoveryManager;
+
   @Override
   public void testRestTransport() throws Exception {
     testDownloadFileAndDeleted();
     testDownloadFileNotDeleted();
     testDownloadFileWithNull();
     testSetContentTypeByResponseEntity();
+    testResponseOKException();
+  }
+
+  private void testResponseOKException() {
+    List<? extends DiscoveryInstance> instances =
+        discoveryManager.findServiceInstances("springmvctest", "springmvc");
+    String endpoint = instances.get(0).getEndpoints().stream()
+        .filter(item -> item.startsWith("rest")).findFirst().get();
+    URI endpointItem = URI.create(endpoint);
+    RestTemplate template = new RestTemplate();
+
+    // This is for compatible usage. For best practise, any status code
+    // should have only one type of response.
+    ResponseEntity<ResponseOKData> resultFail = template.getForEntity(
+        "http://"; + endpointItem.getHost() + ":" + endpointItem.getPort()
+            + "/api/download/testResponseOKExceptionBean?exception=true", 
ResponseOKData.class);
+    TestMgr.check(200, resultFail.getStatusCode().value());
+    TestMgr.check("code-005", resultFail.getBody().getErrorCode());
+    TestMgr.check("error-005", resultFail.getBody().getErrorMessage());
+    ResponseEntity<Boolean> resultOK = template.getForEntity(
+        "http://"; + endpointItem.getHost() + ":" + endpointItem.getPort()
+            + "/api/download/testResponseOKExceptionBean?exception=false", 
boolean.class);
+    TestMgr.check(true, resultOK.getBody());
+
+    resultFail = template.getForEntity(
+        "http://"; + endpointItem.getHost() + ":" + endpointItem.getPort()
+            + 
"/api/download/testResponseOKExceptionDownload?exception=true&content=ddd&contentType=plain/text",
+        ResponseOKData.class);
+    TestMgr.check(200, resultFail.getStatusCode().value());
+    TestMgr.check("code-005", resultFail.getBody().getErrorCode());
+    TestMgr.check("error-005", resultFail.getBody().getErrorMessage());
+
+    ResponseEntity<String> resultPartOK = template.getForEntity(
+        "http://"; + endpointItem.getHost() + ":" + endpointItem.getPort()
+            + 
"/api/download/testResponseOKExceptionDownload?exception=false&content=ddd&contentType=plain/text",
+        String.class);
+    TestMgr.check(200, resultPartOK.getStatusCode().value());
+    TestMgr.check("ddd", resultPartOK.getBody());
   }
 
   private void testDownloadFileAndDeleted() throws Exception {
diff --git 
a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
index cffe5ae85..5db71e59d 100644
--- 
a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
+++ 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
@@ -22,8 +22,6 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
-import jakarta.servlet.http.Part;
-
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.servicecomb.foundation.common.part.FilePart;
@@ -35,6 +33,8 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import jakarta.servlet.http.Part;
+
 @RestSchema(schemaId = "DownloadSchema")
 @RequestMapping(path = "/download", produces = 
MediaType.APPLICATION_JSON_VALUE)
 public class DownloadSchema {
@@ -93,18 +93,45 @@ public class DownloadSchema {
   }
 
   @GetMapping(path = "/setContentTypeByResponseEntity")
-  public ResponseEntity<Part> 
setContentTypeByResponseEntity(@RequestParam("content") String content, 
@RequestParam("contentType") String contentType) throws IOException {
+  public ResponseEntity<Part> 
setContentTypeByResponseEntity(@RequestParam("content") String content,
+      @RequestParam("contentType") String contentType) throws IOException {
     File file = createTempFile(content);
 
     return ResponseEntity
-            .ok()
-            .header(HttpHeaders.CONTENT_TYPE, contentType)
-            .header(HttpHeaders.CONTENT_DISPOSITION, 
"attachment;filename=tempFileEntity.txt")
-            .body(new FilePart(null, file));
+        .ok()
+        .header(HttpHeaders.CONTENT_TYPE, contentType)
+        .header(HttpHeaders.CONTENT_DISPOSITION, 
"attachment;filename=tempFileEntity.txt")
+        .body(new FilePart(null, file));
   }
 
   @GetMapping(path = "/assertLastFileDeleted")
   public boolean assertLastFileDeleted() {
     return lastFile.exists();
   }
+
+  @GetMapping(path = "/testResponseOKExceptionDownload")
+  public ResponseEntity<Part> testResponseOKExceptionDownload(
+      @RequestParam("exception") boolean exception,
+      @RequestParam("content") String content,
+      @RequestParam("contentType") String contentType) throws IOException {
+    if (exception) {
+      throw new ResponseOKException();
+    }
+
+    File file = createTempFile(content);
+
+    return ResponseEntity
+        .ok()
+        .header(HttpHeaders.CONTENT_TYPE, contentType)
+        .header(HttpHeaders.CONTENT_DISPOSITION, 
"attachment;filename=tempFileEntity.txt")
+        .body(new FilePart(null, file));
+  }
+
+  @GetMapping(path = "/testResponseOKExceptionBean")
+  public boolean testResponseOKExceptionBean(@RequestParam("exception") 
boolean exception) {
+    if (exception) {
+      throw new ResponseOKException();
+    }
+    return true;
+  }
 }
diff --git 
a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java
 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java
new file mode 100644
index 000000000..aeadbbeda
--- /dev/null
+++ 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKData.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.demo.springmvc.server;
+
+public class ResponseOKData {
+  private String errorCode;
+
+  private String errorMessage;
+
+  public ResponseOKData(String errorCode, String errorMessage) {
+    this.errorCode = errorCode;
+    this.errorMessage = errorMessage;
+  }
+
+  public String getErrorCode() {
+    return errorCode;
+  }
+
+  public void setErrorCode(String errorCode) {
+    this.errorCode = errorCode;
+  }
+
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  public void setErrorMessage(String errorMessage) {
+    this.errorMessage = errorMessage;
+  }
+}
diff --git 
a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java
 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java
new file mode 100644
index 000000000..6dcafb2db
--- /dev/null
+++ 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKException.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.demo.springmvc.server;
+
+public class ResponseOKException extends RuntimeException {
+}
diff --git 
a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java
 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java
new file mode 100644
index 000000000..7358b7217
--- /dev/null
+++ 
b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/ResponseOKExceptionConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.demo.springmvc.server;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.exception.ExceptionConverter;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.core.Response.StatusType;
+
+public class ResponseOKExceptionConverter implements
+    ExceptionConverter<ResponseOKException> {
+
+  @Override
+  public boolean canConvert(Throwable throwable) {
+    return throwable instanceof ResponseOKException;
+  }
+
+  @Override
+  public InvocationException convert(Invocation invocation, 
ResponseOKException throwable, StatusType genericStatus) {
+    // This is for compatible usage. For best practise, any status code
+    // should have only one type of response.
+    return new InvocationException(Status.OK, new ResponseOKData("code-005", 
"error-005"));
+  }
+}
diff --git 
a/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
 
b/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
new file mode 100644
index 000000000..f25ea28e1
--- /dev/null
+++ 
b/demo/demo-springmvc/springmvc-server/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.servicecomb.demo.springmvc.server.ResponseOKExceptionConverter
diff --git 
a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
 
b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
index 819fc6399..1f165f922 100644
--- 
a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
+++ 
b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
@@ -30,6 +30,7 @@ import org.apache.servicecomb.core.filter.FilterNode;
 import org.apache.servicecomb.core.filter.ProviderFilter;
 import org.apache.servicecomb.foundation.common.utils.AsyncUtils;
 import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import org.apache.servicecomb.transport.highway.message.ResponseHeader;
 
 import io.vertx.core.buffer.Buffer;
@@ -92,8 +93,15 @@ public class HighwayServerCodecFilter extends AbstractFilter 
implements Provider
     ResponseRootSerializer bodySchema = 
operationProtobuf.findResponseRootSerializer(response.getStatusCode());
 
     try {
-      Buffer respBuffer = HighwayCodec.encodeResponse(
-          msgId, header, bodySchema, response.getResult());
+      boolean failed = response.getResult() instanceof InvocationException;
+      Buffer respBuffer;
+      if (failed) {
+        respBuffer = HighwayCodec.encodeResponse(
+            msgId, header, bodySchema, ((InvocationException) 
response.getResult()).getErrorData());
+      } else {
+        respBuffer = HighwayCodec.encodeResponse(
+            msgId, header, bodySchema, response.getResult());
+      }
       transportContext.setResponseBuffer(respBuffer);
 
       invocation.onEncodeResponseFinish();
diff --git 
a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
 
b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
index 60cbf0154..a9356e338 100644
--- 
a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
+++ 
b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
@@ -38,6 +38,7 @@ import 
org.apache.servicecomb.core.invocation.InvocationFactory;
 import org.apache.servicecomb.foundation.common.LegacyPropertyFactory;
 import 
org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace;
 import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import org.apache.servicecomb.transport.highway.message.RequestHeader;
 import org.apache.servicecomb.transport.highway.message.ResponseHeader;
 import org.junit.After;
@@ -145,7 +146,7 @@ public class HighwayServerCodecFilterTest {
     Response response = codecFilter.onFilter(invocation, nextNode).get();
 
     assertThat(response.getStatus()).isEqualTo(INTERNAL_SERVER_ERROR);
-    assertThat(Json.encode(response.getResult()))
+    assertThat(Json.encode(((InvocationException) 
response.getResult()).getErrorData()))
         .isEqualTo("{\"code\":\"SCB.50000000\",\"message\":\"Unexpected "
             + "exception when processing null. encode request failed\"}");
   }

Reply via email to