You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by gu...@apache.org on 2021/08/11 07:57:42 UTC
[dubbo] branch 3.0 updated: Fix 3.0 use triple can not return real
exception (#8458)
This is an automated email from the ASF dual-hosted git repository.
guohao pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/3.0 by this push:
new 6808f8f Fix 3.0 use triple can not return real exception (#8458)
6808f8f is described below
commit 6808f8f104f325bf92cb9715c8c50eaa10ce18eb
Author: earthchen <ea...@gmail.com>
AuthorDate: Wed Aug 11 02:57:32 2021 -0500
Fix 3.0 use triple can not return real exception (#8458)
* Fix use triple can't return real exception in java (#8363)
* Fix use triple can't return real exception in java (#8363)
- add common exception structure for multi-language
* Remove exception serialization and ignore when decode/encode failed
* Add exceptionUtils license and remove useless pom config
* Remove unused constant
* Serialize exception only in wrapper mode
* Format code
* Ignore google rpc classes
Co-authored-by: guohao <gu...@gmail.com>
---
.../rpc/protocol/tri/AbstractClientStream.java | 32 +--
.../rpc/protocol/tri/AbstractServerStream.java | 6 +-
.../dubbo/rpc/protocol/tri/AbstractStream.java | 88 ++++++--
.../dubbo/rpc/protocol/tri/ClientStream.java | 10 +-
.../dubbo/rpc/protocol/tri/DefaultMetadata.java | 7 +
.../dubbo/rpc/protocol/tri/ExceptionUtils.java | 77 +++++++
.../apache/dubbo/rpc/protocol/tri/GrpcStatus.java | 17 ++
.../dubbo/rpc/protocol/tri/Http2HeaderMeta.java | 5 +
.../apache/dubbo/rpc/protocol/tri/Metadata.java | 2 +
.../rpc/protocol/tri/TripleClientHandler.java | 2 +-
.../dubbo/rpc/protocol/tri/TripleConstant.java | 7 +
.../dubbo/rpc/protocol/tri/TripleRpcException.java | 46 ++++
.../apache/dubbo/rpc/protocol/tri/TripleUtil.java | 69 ++++++
.../dubbo/rpc/protocol/tri/UnaryClientStream.java | 71 +++++-
.../src/main/proto/error_details.proto | 251 +++++++++++++++++++++
.../dubbo-rpc-triple/src/main/proto/status.proto | 93 ++++++++
.../src/main/proto/triple_wrapper.proto | 11 +-
pom.xml | 1 +
18 files changed, 743 insertions(+), 52 deletions(-)
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java
index 967e14a..85e3d34 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java
@@ -77,13 +77,13 @@ public abstract class AbstractClientStream extends AbstractStream implements Str
} catch (RejectedExecutionException e) {
LOGGER.error("Consumer's thread pool is full", e);
getStreamSubscriber().onError(GrpcStatus.fromCode(GrpcStatus.Code.RESOURCE_EXHAUSTED)
- .withDescription("Consumer's thread pool is full").asException());
+ .withDescription("Consumer's thread pool is full").asException());
} catch (Throwable t) {
LOGGER.error("Consumer submit request to thread pool error ", t);
getStreamSubscriber().onError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL)
- .withCause(t)
- .withDescription("Consumer's error")
- .asException());
+ .withCause(t)
+ .withDescription("Consumer's error")
+ .asException());
}
}
@@ -128,11 +128,11 @@ public abstract class AbstractClientStream extends AbstractStream implements Str
}
if (getMethodDescriptor().isNeedWrap()) {
final TripleWrapper.TripleResponseWrapper wrapper = TripleUtil.unpack(data,
- TripleWrapper.TripleResponseWrapper.class);
+ TripleWrapper.TripleResponseWrapper.class);
if (!getSerializeType().equals(TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType()))) {
throw new UnsupportedOperationException("Received inconsistent serialization type from server, " +
- "reject to deserialize! Expected:" + getSerializeType() +
- " Actual:" + TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType()));
+ "reject to deserialize! Expected:" + getSerializeType() +
+ " Actual:" + TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType()));
}
return TripleUtil.unwrapResp(getUrl(), wrapper, getMultipleSerialization());
} else {
@@ -146,17 +146,17 @@ public abstract class AbstractClientStream extends AbstractStream implements Str
protected Metadata createRequestMeta(RpcInvocation inv) {
Metadata metadata = new DefaultMetadata();
metadata.put(TripleConstant.PATH_KEY, "/" + inv.getObjectAttachment(CommonConstants.PATH_KEY) + "/" + inv.getMethodName())
- .put(TripleConstant.AUTHORITY_KEY, getUrl().getAddress())
- .put(TripleConstant.CONTENT_TYPE_KEY, TripleConstant.CONTENT_PROTO)
- .put(TripleConstant.TIMEOUT, inv.get(CommonConstants.TIMEOUT_KEY) + "m")
- .put(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
+ .put(TripleConstant.AUTHORITY_KEY, getUrl().getAddress())
+ .put(TripleConstant.CONTENT_TYPE_KEY, TripleConstant.CONTENT_PROTO)
+ .put(TripleConstant.TIMEOUT, inv.get(CommonConstants.TIMEOUT_KEY) + "m")
+ .put(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
metadata.putIfNotNull(TripleConstant.SERVICE_VERSION, inv.getInvoker().getUrl().getVersion())
- .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY,
- (String) inv.getObjectAttachments().remove(CommonConstants.APPLICATION_KEY))
- .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY,
- (String) inv.getObjectAttachments().remove(CommonConstants.REMOTE_APPLICATION_KEY))
- .putIfNotNull(TripleConstant.SERVICE_GROUP, inv.getInvoker().getUrl().getGroup());
+ .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY,
+ (String) inv.getObjectAttachments().remove(CommonConstants.APPLICATION_KEY))
+ .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY,
+ (String) inv.getObjectAttachments().remove(CommonConstants.REMOTE_APPLICATION_KEY))
+ .putIfNotNull(TripleConstant.SERVICE_GROUP, inv.getInvoker().getUrl().getGroup());
inv.getObjectAttachments().remove(CommonConstants.GROUP_KEY);
inv.getObjectAttachments().remove(CommonConstants.INTERFACE_KEY);
inv.getObjectAttachments().remove(CommonConstants.PATH_KEY);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java
index 974d088..0925c05 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java
@@ -146,9 +146,9 @@ public abstract class AbstractServerStream extends AbstractStream implements Str
TripleWrapper.TripleRequestWrapper.class);
if (!getSerializeType().equals(TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType()))) {
transportError(GrpcStatus.fromCode(GrpcStatus.Code.INVALID_ARGUMENT)
- .withDescription("Received inconsistent serialization type from client, " +
- "reject to deserialize! Expected:" + getSerializeType() +
- " Actual:" + TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType())));
+ .withDescription("Received inconsistent serialization type from client, " +
+ "reject to deserialize! Expected:" + getSerializeType() +
+ " Actual:" + TripleUtil.convertHessianFromWrapper(wrapper.getSerializeType())));
return null;
}
if (getMethodDescriptor() == null) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
index eb685c9..81f1b44 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
@@ -24,12 +24,17 @@ import org.apache.dubbo.common.serialize.MultipleSerialization;
import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.common.threadlocal.NamedInternalThreadFactory;
import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.config.Constants;
import org.apache.dubbo.remoting.exchange.Request;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import org.apache.dubbo.rpc.model.ServiceDescriptor;
import org.apache.dubbo.rpc.protocol.tri.GrpcStatus.Code;
+import org.apache.dubbo.triple.TripleWrapper;
+import com.google.protobuf.Any;
+import com.google.rpc.DebugInfo;
+import com.google.rpc.Status;
import io.netty.handler.codec.http2.Http2Headers;
import java.io.IOException;
@@ -54,7 +59,8 @@ public abstract class AbstractStream implements Stream {
static {
ThreadFactory tripleTF = new NamedInternalThreadFactory("tri-callbcak", true);
for (int i = 0; i < 4; i++) {
- final ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<>(1024),
+ final ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
+ new LinkedBlockingQueue<>(1024),
tripleTF, new ThreadPoolExecutor.AbortPolicy());
CALLBACK_EXECUTORS.add(tp);
}
@@ -189,26 +195,81 @@ public abstract class AbstractStream implements Stream {
}
protected void transportError(GrpcStatus status) {
- Metadata metadata = new DefaultMetadata();
- metadata.put(TripleConstant.STATUS_KEY, Integer.toString(status.code.code));
- metadata.put(TripleConstant.MESSAGE_KEY, status.toMessage());
- getTransportSubscriber().tryOnMetadata(metadata, true);
+ // set metadata
+ Metadata metadata = getMetaData(status);
+ getTransportSubscriber().tryOnMetadata(metadata, false);
+ // set trailers
+ Metadata trailers = getTrailers(status);
+ getTransportSubscriber().tryOnMetadata(trailers, true);
if (LOGGER.isErrorEnabled()) {
LOGGER.error("[Triple-Server-Error] " + status.toMessage());
}
}
protected void transportError(Throwable throwable) {
- Metadata metadata = new DefaultMetadata();
- metadata.put(TripleConstant.STATUS_KEY, Integer.toString(Code.UNKNOWN.code));
- metadata.put(TripleConstant.MESSAGE_KEY, throwable.getMessage());
- getTransportSubscriber().tryOnMetadata(metadata, true);
+ GrpcStatus status = new GrpcStatus(Code.UNKNOWN, throwable, throwable.getMessage());
+ Metadata metadata = getMetaData(status);
+ getTransportSubscriber().tryOnMetadata(metadata, false);
+ Metadata trailers = getTrailers(status);
+ getTransportSubscriber().tryOnMetadata(trailers, true);
if (LOGGER.isErrorEnabled()) {
LOGGER.error("[Triple-Server-Error] service=" + getServiceDescriptor().getServiceName()
+ " method=" + getMethodName(), throwable);
}
}
+ private Metadata getMetaData(GrpcStatus status) {
+ Metadata metadata = new DefaultMetadata();
+ metadata.put(TripleConstant.MESSAGE_KEY, getGrpcMessage(status));
+ metadata.put(TripleConstant.STATUS_KEY, String.valueOf(status.code.code));
+ return metadata;
+ }
+
+ private String getGrpcMessage(GrpcStatus status) {
+ if (StringUtils.isNotEmpty(status.description)) {
+ return status.description;
+ }
+ if (status.cause != null) {
+ return status.cause.getMessage();
+ }
+ return "unknown";
+ }
+
+ private Metadata getTrailers(GrpcStatus grpcStatus) {
+
+ Metadata metadata = new DefaultMetadata();
+ Status.Builder builder = Status.newBuilder()
+ .setCode(grpcStatus.code.code)
+ .setMessage(getGrpcMessage(grpcStatus));
+ Throwable throwable = grpcStatus.cause;
+ if (throwable == null) {
+ return metadata;
+ }
+ DebugInfo debugInfo = DebugInfo.newBuilder()
+ .addAllStackEntries(ExceptionUtils.getStackFrameList(throwable))
+ // can not use now
+ // .setDetail(throwable.getMessage())
+ .build();
+ builder.addDetails(Any.pack(debugInfo));
+ Status status = builder.build();
+ metadata.put(TripleConstant.STATUS_DETAIL_KEY, TripleUtil.encodeBase64ASCII(status.toByteArray()));
+ // only wrapper mode support exception serialization
+ if (getMethodDescriptor() != null && !getMethodDescriptor().isNeedWrap()) {
+ return metadata;
+ }
+ try {
+ TripleWrapper.TripleExceptionWrapper exceptionWrapper = TripleUtil.wrapException(getUrl(), throwable, getSerializeType(), getMultipleSerialization());
+ String exceptionStr = TripleUtil.encodeBase64ASCII(exceptionWrapper.toByteArray());
+ if (exceptionStr.length() <= TripleConstant.DEFAULT_HEADER_LIST_SIZE) {
+ metadata.put(TripleConstant.EXCEPTION_TW_BIN, exceptionStr);
+ }
+ } catch (Throwable t) {
+ LOGGER.warn("Encode triple exception to trailers failed", t);
+ }
+
+ return metadata;
+ }
+
protected Map<String, Object> parseMetadataToMap(Metadata metadata) {
Map<String, Object> attachments = new LinkedHashMap<>();
for (Map.Entry<CharSequence, CharSequence> header : metadata) {
@@ -323,14 +384,7 @@ public abstract class AbstractStream implements Stream {
@Override
public void onComplete(OperationHandler handler) {
- Metadata metadata;
- if (getTrailers() == null) {
- metadata = getHeaders();
- } else {
- metadata = getTrailers();
- }
-
- final GrpcStatus status = extractStatusFromMeta(metadata);
+ final GrpcStatus status = extractStatusFromMeta(getHeaders());
if (GrpcStatus.Code.isOk(status.code.code)) {
doOnComplete(handler);
} else {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java
index 7352bf1..a69bd6b 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java
@@ -65,18 +65,12 @@ public class ClientStream extends AbstractClientStream implements Stream {
@Override
public void onComplete(OperationHandler handler) {
execute(() -> {
- Metadata metadata;
- if (getTrailers() == null) {
- metadata = getHeaders();
- } else {
- metadata = getTrailers();
- }
- final GrpcStatus status = extractStatusFromMeta(metadata);
+ final GrpcStatus status = extractStatusFromMeta(getHeaders());
if (GrpcStatus.Code.isOk(status.code.code)) {
getStreamSubscriber().onCompleted();
} else {
- getStreamSubscriber().onError(status.asException());
+ getStreamSubscriber().onError(status.asException(getTrailers()));
}
});
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java
index 5873fd1..c05ff36 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java
@@ -52,8 +52,15 @@ public class DefaultMetadata implements Metadata {
return innerMap.entrySet().spliterator();
}
+ @Override
public boolean contains(CharSequence key) {
return innerMap.containsKey(key);
}
+ @Override
+ public boolean remove(CharSequence key) {
+ innerMap.remove(key);
+ return true;
+ }
+
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ExceptionUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ExceptionUtils.java
new file mode 100644
index 0000000..ed37cfa
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ExceptionUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dubbo.rpc.protocol.tri;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class ExceptionUtils {
+
+ private static final int NOT_FOUND = -1;
+
+ public static String getStackTrace(final Throwable throwable) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ throwable.printStackTrace(pw);
+ return sw.getBuffer().toString();
+ }
+
+ public static String getStackFrameString(List<String> stackFrameList) {
+ if (CollectionUtils.isEmpty(stackFrameList)) {
+ return "";
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ for (String s : stackFrameList) {
+ stringBuilder.append(s).append("\n");
+ }
+ return stringBuilder.toString();
+ }
+
+ public static String[] getStackFrames(final Throwable throwable) {
+ if (throwable == null) {
+ return new String[0];
+ }
+ return getStackFrames(getStackTrace(throwable));
+ }
+
+ static String[] getStackFrames(final String stackTrace) {
+ final String linebreak = System.lineSeparator();
+ final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ final List<String> list = new ArrayList<>();
+ while (frames.hasMoreTokens()) {
+ list.add(frames.nextToken());
+ }
+ return list.toArray(new String[0]);
+ }
+
+ public static List<String> getStackFrameList(final Throwable t) {
+ final String stackTrace = getStackTrace(t);
+ final String linebreak = System.lineSeparator();
+ final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+ final List<String> list = new ArrayList<>();
+ while (frames.hasMoreTokens()) {
+ list.add(frames.nextToken());
+ }
+ return list;
+ }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java
index 669b90d..40d6e86 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java
@@ -22,6 +22,8 @@ import org.apache.dubbo.remoting.exchange.Response;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.QueryStringEncoder;
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
/**
* See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
*/
@@ -95,6 +97,17 @@ public class GrpcStatus {
return QueryStringDecoder.decodeComponent(raw);
}
+ public static Metadata trailersFromThrowable(Throwable t) {
+ Throwable cause = checkNotNull(t, "t");
+ while (cause != null) {
+ if (cause instanceof TripleRpcException) {
+ return ((TripleRpcException) cause).getTrailers();
+ }
+ cause = cause.getCause();
+ }
+ return null;
+ }
+
public GrpcStatus withCause(Throwable cause) {
return new GrpcStatus(this.code, cause, this.description);
}
@@ -107,6 +120,10 @@ public class GrpcStatus {
return new TripleRpcException(this);
}
+ public TripleRpcException asException(Metadata trailers) {
+ return new TripleRpcException(this, trailers);
+ }
+
public String toMessage() {
final String msg;
if (cause == null) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java
index 8b06996..24ef489 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java
@@ -46,6 +46,11 @@ public class Http2HeaderMeta implements Metadata {
}
@Override
+ public boolean remove(CharSequence key) {
+ return headers.remove(key);
+ }
+
+ @Override
public Iterator<Map.Entry<CharSequence, CharSequence>> iterator() {
return headers.iterator();
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java
index a4e926c..cb16191 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java
@@ -34,4 +34,6 @@ public interface Metadata extends Iterable<Map.Entry<CharSequence, CharSequence>
boolean contains(CharSequence key);
+ boolean remove(CharSequence key);
+
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java
index f7faaf1..25721fe 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java
@@ -71,7 +71,7 @@ public class TripleClientHandler extends ChannelDuplexHandler {
MethodDescriptor methodDescriptor = repo.lookupMethod(inv.getServiceName(), inv.getMethodName());
String serviceKey = url.getServiceKey();
// If it is InstanceAddressURL, the serviceKey may not be obtained.
- if(null == serviceKey) {
+ if (null == serviceKey) {
serviceKey = inv.getTargetServiceUniqueName();
}
final ConsumerModel service = repo.lookupReferredService(serviceKey);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
index 673897f..96da5b6 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
@@ -16,12 +16,15 @@
*/
package org.apache.dubbo.rpc.protocol.tri;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+
public interface TripleConstant {
String AUTHORITY_KEY = ":authority";
String PATH_KEY = ":path";
String HTTP_STATUS_KEY = "http-status";
String STATUS_KEY = "grpc-status";
String MESSAGE_KEY = "grpc-message";
+ String STATUS_DETAIL_KEY = "grpc-status-details-bin";
String TIMEOUT = "grpc-timeout";
String CONTENT_TYPE_KEY = "content-type";
String CONTENT_PROTO = "application/grpc+proto";
@@ -33,5 +36,9 @@ public interface TripleConstant {
String SERVICE_VERSION = "tri-service-version";
String SERVICE_GROUP = "tri-service-group";
String TRI_VERSION = "1.0.0";
+ String EXCEPTION_TW_BIN = "tri-exception-tw-bin";
+ String DEFAULT_TRIPLE_USER_EXCEPTION_SERIALIZATION = "hessian2";
+ // each header size
+ long DEFAULT_HEADER_LIST_SIZE = Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleRpcException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleRpcException.java
index fbed3e7..85a2c56 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleRpcException.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleRpcException.java
@@ -20,13 +20,59 @@ import org.apache.dubbo.rpc.RpcException;
public class TripleRpcException extends RpcException {
private final GrpcStatus status;
+ private final Metadata trailers;
+ private final boolean fillInStackTrace;
+ private int code;
+
+ public TripleRpcException(int code, String msg) {
+ this(code, msg, null);
+ }
+
+ public TripleRpcException(int code, String msg, Metadata trailers) {
+ super(msg);
+ this.code = code;
+ this.status = null;
+ this.trailers = trailers;
+ this.fillInStackTrace = false;
+ }
public TripleRpcException(GrpcStatus status) {
+ this(status, null);
+ }
+
+ public TripleRpcException(GrpcStatus status, Metadata trailers) {
+ this(status, trailers, true);
+ }
+
+ public TripleRpcException(GrpcStatus status, Metadata trailers, boolean fillInStackTrace) {
super(status.description, status.cause);
this.status = status;
+ this.trailers = trailers;
+ this.fillInStackTrace = fillInStackTrace;
+ this.code = status.code.code;
+ fillInStackTrace();
+ }
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ return fillInStackTrace ? super.fillInStackTrace() : this;
+ }
+
+ @Override
+ public int getCode() {
+ return code;
+ }
+
+ @Override
+ public void setCode(int code) {
+ this.code = code;
}
public GrpcStatus getStatus() {
return status;
}
+
+ public Metadata getTrailers() {
+ return trailers;
+ }
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java
index 1016954..f8f480b 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java
@@ -23,8 +23,11 @@ import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import org.apache.dubbo.triple.TripleWrapper;
+import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.rpc.DebugInfo;
+import com.google.rpc.ErrorInfo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
@@ -42,6 +45,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
@@ -51,6 +57,10 @@ public class TripleUtil {
"tri_server_stream");
public static final AttributeKey<AbstractClientStream> CLIENT_STREAM_KEY = AttributeKey.newInstance(
"tri_client_stream");
+
+ public static final String LANGUAGE = "java";
+
+
private static final SingleProtobufSerialization pbSerialization = new SingleProtobufSerialization();
private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder().withoutPadding();
@@ -106,6 +116,26 @@ public class TripleUtil {
}
}
+ public static Map<Class<?>, Object> tranFromStatusDetails(List<Any> detailList) {
+ Map<Class<?>, Object> map = new HashMap<>();
+ try {
+ for (Any any : detailList) {
+ if (any.is(ErrorInfo.class)) {
+ ErrorInfo errorInfo = any.unpack(ErrorInfo.class);
+ map.putIfAbsent(ErrorInfo.class, errorInfo);
+ } else if (any.is(DebugInfo.class)) {
+ DebugInfo debugInfo = any.unpack(DebugInfo.class);
+ map.putIfAbsent(DebugInfo.class, debugInfo);
+ }
+ // support others type but now only support this
+ }
+ } catch (InvalidProtocolBufferException e) {
+ e.printStackTrace();
+ }
+ return map;
+ }
+
+
public static Object[] unwrapReq(URL url, TripleWrapper.TripleRequestWrapper wrap,
MultipleSerialization multipleSerialization) {
String serializeType = convertHessianFromWrapper(wrap.getSerializeType());
@@ -140,6 +170,45 @@ public class TripleUtil {
}
}
+ public static TripleWrapper.TripleExceptionWrapper wrapException(URL url, Throwable throwable,
+ String serializeType,
+ MultipleSerialization serialization) {
+ try {
+ final TripleWrapper.TripleExceptionWrapper.Builder builder = TripleWrapper.TripleExceptionWrapper.newBuilder()
+ .setLanguage(LANGUAGE)
+ .setClassName(throwable.getClass().getName())
+ .setSerialization(serializeType);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ serialization.serialize(url, serializeType, builder.getClassName(), throwable, bos);
+ builder.setData(ByteString.copyFrom(bos.toByteArray()));
+ bos.close();
+ return builder.build();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to pack wrapper exception", e);
+ }
+ }
+
+ public static Throwable unWrapException(URL url, TripleWrapper.TripleExceptionWrapper wrap,
+ String serializeType,
+ MultipleSerialization serialization) {
+ if (wrap == null) {
+ return null;
+ }
+ if (!LANGUAGE.equals(wrap.getLanguage())) {
+ return null;
+ }
+ try {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(wrap.getData().toByteArray());
+ Object obj = serialization.deserialize(url, serializeType, wrap.getClassName(), bais);
+ bais.close();
+ return (Throwable) obj;
+ } catch (Exception e) {
+ // if this null ,can get common exception
+ return null;
+ }
+ }
+
+
public static TripleWrapper.TripleRequestWrapper wrapReq(URL url, String serializeType, Object req,
String type,
MultipleSerialization multipleSerialization) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java
index 6de44eb..d43d241 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java
@@ -22,7 +22,14 @@ import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.remoting.exchange.Response;
import org.apache.dubbo.remoting.exchange.support.DefaultFuture2;
import org.apache.dubbo.rpc.AppResponse;
+import org.apache.dubbo.triple.TripleWrapper;
+import com.google.protobuf.Any;
+import com.google.rpc.DebugInfo;
+import com.google.rpc.Status;
+
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
public class UnaryClientStream extends AbstractClientStream implements Stream {
@@ -63,16 +70,68 @@ public class UnaryClientStream extends AbstractClientStream implements Stream {
});
}
+ @Override
protected void onError(GrpcStatus status) {
Response response = new Response(getRequest().getId(), TripleConstant.TRI_VERSION);
- if (status.description != null) {
- response.setErrorMessage(status.description);
- } else {
- response.setErrorMessage(status.cause.getMessage());
+ response.setErrorMessage(status.description);
+ final AppResponse result = new AppResponse();
+ result.setException(getThrowable(this.getTrailers()));
+ result.setObjectAttachments(UnaryClientStream.this.parseMetadataToMap(this.getTrailers()));
+ response.setResult(result);
+ if (!result.hasException()) {
+ final byte code = GrpcStatus.toDubboStatus(status.code);
+ response.setStatus(code);
}
- final byte code = GrpcStatus.toDubboStatus(status.code);
- response.setStatus(code);
DefaultFuture2.received(getConnection(), response);
}
+
+ private Throwable getThrowable(Metadata metadata) {
+ // first get throwable from exception tw bin
+ try {
+ if (metadata.contains(TripleConstant.EXCEPTION_TW_BIN)) {
+ final CharSequence raw = metadata.get(TripleConstant.EXCEPTION_TW_BIN);
+ byte[] exceptionTwBin = TripleUtil.decodeASCIIByte(raw);
+ ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+ try {
+ TripleWrapper.TripleExceptionWrapper wrapper = TripleUtil.unpack(exceptionTwBin,
+ TripleWrapper.TripleExceptionWrapper.class);
+ Throwable throwable = TripleUtil.unWrapException(getUrl(), wrapper, getSerializeType(),
+ getMultipleSerialization());
+ if (throwable != null) {
+ return throwable;
+ }
+ } finally {
+ ClassLoadUtil.switchContextLoader(tccl);
+ }
+ // avoid subsequent parse header problems
+ metadata.remove(TripleConstant.EXCEPTION_TW_BIN);
+ }
+ } catch (Throwable t) {
+ LOGGER.warn(String.format("Decode exception instance from triple trailers:%s failed", metadata), t);
+ }
+ // second get status detail
+ if (metadata.contains(TripleConstant.STATUS_DETAIL_KEY)) {
+ final CharSequence raw = metadata.get(TripleConstant.STATUS_DETAIL_KEY);
+ byte[] statusDetailBin = TripleUtil.decodeASCIIByte(raw);
+ ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+ try {
+ final Status statusDetail = TripleUtil.unpack(statusDetailBin, Status.class);
+ List<Any> detailList = statusDetail.getDetailsList();
+ Map<Class<?>, Object> classObjectMap = TripleUtil.tranFromStatusDetails(detailList);
+
+ // get common exception from DebugInfo
+ DebugInfo debugInfo = (DebugInfo) classObjectMap.get(DebugInfo.class);
+ if (debugInfo == null) {
+ return new TripleRpcException(statusDetail.getCode(),
+ statusDetail.getMessage(), metadata);
+ }
+ String msg = ExceptionUtils.getStackFrameString(debugInfo.getStackEntriesList());
+ return new TripleRpcException(statusDetail.getCode(), msg, metadata);
+ } finally {
+ ClassLoadUtil.switchContextLoader(tccl);
+ }
+ }
+ return null;
+ }
}
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/error_details.proto b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/error_details.proto
new file mode 100644
index 0000000..c260a42
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/error_details.proto
@@ -0,0 +1,251 @@
+// Copyright 2020 Google LLC
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package google.rpc;
+
+import "google/protobuf/duration.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails";
+option java_multiple_files = true;
+option java_outer_classname = "ErrorDetailsProto";
+option java_package = "com.google.rpc";
+option objc_class_prefix = "RPC";
+
+// Describes when the clients can retry a failed request. Clients could ignore
+// the recommendation here or retry when this information is missing from error
+// responses.
+//
+// It's always recommended that clients should use exponential backoff when
+// retrying.
+//
+// Clients should wait until `retry_delay` amount of time has passed since
+// receiving the error response before retrying. If retrying requests also
+// fail, clients should use an exponential backoff scheme to gradually increase
+// the delay between retries based on `retry_delay`, until either a maximum
+// number of retries have been reached or a maximum retry delay cap has been
+// reached.
+message RetryInfo {
+ // Clients should wait at least this long between retrying the same request.
+ google.protobuf.Duration retry_delay = 1;
+}
+
+// Describes additional debugging info.
+message DebugInfo {
+ // The stack trace entries indicating where the error occurred.
+ repeated string stack_entries = 1;
+
+ // Additional debugging information provided by the server.
+ string detail = 2;
+}
+
+// Describes how a quota check failed.
+//
+// For example if a daily limit was exceeded for the calling project,
+// a service could respond with a QuotaFailure detail containing the project
+// id and the description of the quota limit that was exceeded. If the
+// calling project hasn't enabled the service in the developer console, then
+// a service could respond with the project id and set `service_disabled`
+// to true.
+//
+// Also see RetryInfo and Help types for other details about handling a
+// quota failure.
+message QuotaFailure {
+ // A message type used to describe a single quota violation. For example, a
+ // daily quota or a custom quota that was exceeded.
+ message Violation {
+ // The subject on which the quota check failed.
+ // For example, "clientip:<ip address of client>" or "project:<Google
+ // developer project id>".
+ string subject = 1;
+
+ // A description of how the quota check failed. Clients can use this
+ // description to find more about the quota configuration in the service's
+ // public documentation, or find the relevant quota limit to adjust through
+ // developer console.
+ //
+ // For example: "Service disabled" or "Daily Limit for read operations
+ // exceeded".
+ string description = 2;
+ }
+
+ // Describes all quota violations.
+ repeated Violation violations = 1;
+}
+
+// Describes the cause of the error with structured details.
+//
+// Example of an error when contacting the "pubsub.googleapis.com" API when it
+// is not enabled:
+//
+// { "reason": "API_DISABLED"
+// "domain": "googleapis.com"
+// "metadata": {
+// "resource": "projects/123",
+// "service": "pubsub.googleapis.com"
+// }
+// }
+//
+// This response indicates that the pubsub.googleapis.com API is not enabled.
+//
+// Example of an error that is returned when attempting to create a Spanner
+// instance in a region that is out of stock:
+//
+// { "reason": "STOCKOUT"
+// "domain": "spanner.googleapis.com",
+// "metadata": {
+// "availableRegions": "us-central1,us-east2"
+// }
+// }
+message ErrorInfo {
+ // The reason of the error. This is a constant value that identifies the
+ // proximate cause of the error. Error reasons are unique within a particular
+ // domain of errors. This should be at most 63 characters and match
+ // /[A-Z0-9_]+/.
+ string reason = 1;
+
+ // The logical grouping to which the "reason" belongs. The error domain
+ // is typically the registered service name of the tool or product that
+ // generates the error. Example: "pubsub.googleapis.com". If the error is
+ // generated by some common infrastructure, the error domain must be a
+ // globally unique value that identifies the infrastructure. For Google API
+ // infrastructure, the error domain is "googleapis.com".
+ string domain = 2;
+
+ // Additional structured details about this error.
+ //
+ // Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in
+ // length. When identifying the current value of an exceeded limit, the units
+ // should be contained in the key, not the value. For example, rather than
+ // {"instanceLimit": "100/request"}, should be returned as,
+ // {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
+ // instances that can be created in a single (batch) request.
+ map<string, string> metadata = 3;
+}
+
+// Describes what preconditions have failed.
+//
+// For example, if an RPC failed because it required the Terms of Service to be
+// acknowledged, it could list the terms of service violation in the
+// PreconditionFailure message.
+message PreconditionFailure {
+ // A message type used to describe a single precondition failure.
+ message Violation {
+ // The type of PreconditionFailure. We recommend using a service-specific
+ // enum type to define the supported precondition violation subjects. For
+ // example, "TOS" for "Terms of Service violation".
+ string type = 1;
+
+ // The subject, relative to the type, that failed.
+ // For example, "google.com/cloud" relative to the "TOS" type would indicate
+ // which terms of service is being referenced.
+ string subject = 2;
+
+ // A description of how the precondition failed. Developers can use this
+ // description to understand how to fix the failure.
+ //
+ // For example: "Terms of service not accepted".
+ string description = 3;
+ }
+
+ // Describes all precondition violations.
+ repeated Violation violations = 1;
+}
+
+// Describes violations in a client request. This error type focuses on the
+// syntactic aspects of the request.
+message BadRequest {
+ // A message type used to describe a single bad request field.
+ message FieldViolation {
+ // A path leading to a field in the request body. The value will be a
+ // sequence of dot-separated identifiers that identify a protocol buffer
+ // field. E.g., "field_violations.field" would identify this field.
+ string field = 1;
+
+ // A description of why the request element is bad.
+ string description = 2;
+ }
+
+ // Describes all violations in a client request.
+ repeated FieldViolation field_violations = 1;
+}
+
+// Contains metadata about the request that clients can attach when filing a bug
+// or providing other forms of feedback.
+message RequestInfo {
+ // An opaque string that should only be interpreted by the service generating
+ // it. For example, it can be used to identify requests in the service's logs.
+ string request_id = 1;
+
+ // Any data that was used to serve this request. For example, an encrypted
+ // stack trace that can be sent back to the service provider for debugging.
+ string serving_data = 2;
+}
+
+// Describes the resource that is being accessed.
+message ResourceInfo {
+ // A name for the type of resource being accessed, e.g. "sql table",
+ // "cloud storage bucket", "file", "Google calendar"; or the type URL
+ // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
+ string resource_type = 1;
+
+ // The name of the resource being accessed. For example, a shared calendar
+ // name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
+ // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
+ string resource_name = 2;
+
+ // The owner of the resource (optional).
+ // For example, "user:<owner email>" or "project:<Google developer project
+ // id>".
+ string owner = 3;
+
+ // Describes what error is encountered when accessing this resource.
+ // For example, updating a cloud project may require the `writer` permission
+ // on the developer console project.
+ string description = 4;
+}
+
+// Provides links to documentation or for performing an out of band action.
+//
+// For example, if a quota check failed with an error indicating the calling
+// project hasn't enabled the accessed service, this can contain a URL pointing
+// directly to the right place in the developer console to flip the bit.
+message Help {
+ // Describes a URL link.
+ message Link {
+ // Describes what the link offers.
+ string description = 1;
+
+ // The URL of the link.
+ string url = 2;
+ }
+
+ // URL(s) pointing to additional information on handling the current error.
+ repeated Link links = 1;
+}
+
+// Provides a localized error message that is safe to return to the user
+// which can be attached to an RPC error.
+message LocalizedMessage {
+ // The locale used following the specification defined at
+ // http://www.rfc-editor.org/rfc/bcp/bcp47.txt.
+ // Examples are: "en-US", "fr-CH", "es-MX"
+ string locale = 1;
+
+ // The localized error message in the above locale.
+ string message = 2;
+}
+
+
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/status.proto b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/status.proto
new file mode 100644
index 0000000..b4e2a01
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/status.proto
@@ -0,0 +1,93 @@
+// Copyright 2016 Google Inc.
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package google.rpc;
+
+import "google/protobuf/any.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";
+option java_multiple_files = true;
+option java_outer_classname = "StatusProto";
+option java_package = "com.google.rpc";
+option objc_class_prefix = "RPC";
+
+
+// The `Status` type defines a logical error model that is suitable for different
+// programming environments, including REST APIs and RPC APIs. It is used by
+// [gRPC](https://github.com/grpc). The error model is designed to be:
+//
+// - Simple to use and understand for most users
+// - Flexible enough to meet unexpected needs
+//
+// # Overview
+//
+// The `Status` message contains three pieces of data: error code, error message,
+// and error details. The error code should be an enum value of
+// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The
+// error message should be a developer-facing English message that helps
+// developers *understand* and *resolve* the error. If a localized user-facing
+// error message is needed, put the localized message in the error details or
+// localize it in the client. The optional error details may contain arbitrary
+// information about the error. There is a predefined set of error detail types
+// in the package `google.rpc` which can be used for common error conditions.
+//
+// # Language mapping
+//
+// The `Status` message is the logical representation of the error model, but it
+// is not necessarily the actual wire format. When the `Status` message is
+// exposed in different client libraries and different wire protocols, it can be
+// mapped differently. For example, it will likely be mapped to some exceptions
+// in Java, but more likely mapped to some error codes in C.
+//
+// # Other uses
+//
+// The error model and the `Status` message can be used in a variety of
+// environments, either with or without APIs, to provide a
+// consistent developer experience across different environments.
+//
+// Example uses of this error model include:
+//
+// - Partial errors. If a service needs to return partial errors to the client,
+// it may embed the `Status` in the normal response to indicate the partial
+// errors.
+//
+// - Workflow errors. A typical workflow has multiple steps. Each step may
+// have a `Status` message for error reporting purpose.
+//
+// - Batch operations. If a client uses batch request and batch response, the
+// `Status` message should be used directly inside batch response, one for
+// each error sub-response.
+//
+// - Asynchronous operations. If an API call embeds asynchronous operation
+// results in its response, the status of those operations should be
+// represented directly using the `Status` message.
+//
+// - Logging. If some API errors are stored in logs, the message `Status` could
+// be used directly after any stripping needed for security/privacy reasons.
+message Status {
+ // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
+ int32 code = 1;
+
+ // A developer-facing error message, which should be in English. Any
+ // user-facing error message should be localized and sent in the
+ // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
+ string message = 2;
+
+ // A list of messages that carry the error details. There will be a
+ // common set of message types for APIs to use.
+ repeated google.protobuf.Any details = 3;
+}
+
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/triple_wrapper.proto b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/triple_wrapper.proto
index 5f12ae8..0ad80f5 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/triple_wrapper.proto
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/triple_wrapper.proto
@@ -14,6 +14,8 @@
// limitations under the License.
syntax = "proto3";
+import "google/protobuf/any.proto";
+
package org.apache.dubbo.triple;
message TripleRequestWrapper {
@@ -28,4 +30,11 @@ message TripleResponseWrapper {
string serializeType = 1;
bytes data = 2;
string type = 3;
-}
\ No newline at end of file
+}
+
+message TripleExceptionWrapper {
+ string language = 1;
+ string serialization = 2;
+ string className = 3;
+ bytes data = 4;
+}
diff --git a/pom.xml b/pom.xml
index 034699f..d7c309a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -318,6 +318,7 @@
**/org/apache/dubbo/triple/TripleWrapper.java,
**/istio/v1/auth/Ca.java,
**/istio/v1/auth/IstioCertificateServiceGrpc.java,
+ **/com/google/rpc/*,
**/generated/**/*,
**/target/**/*,
**/*.json