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